WPF MVVM UserControl 绑定

WPF MVVM UserControl Binding

提问人:Michael Riley 提问时间:11/13/2023 最后编辑:ClemensMichael Riley 更新时间:11/13/2023 访问量:63

问:

大家晚上好。我与我一直在写的程序有点束缚。我的客户上周做了一些更改,我认为这些更改在 MVVM 架构中会更好地实现。事实上,我认为我的整个应用程序在 MVVM 中会更好。一开始,需求很小,所以我没有实现MVVM,因为我没有很好地实践它。

我一直在研究 stackoverflow、stack exchange、youtube 以及我能找到的几乎所有其他东西,以便在 MVVM 上快速启动。我已经阅读了许多关于UserControl绑定的帖子,但是我缺少一些拼图,需要取消阻止。因此,如果我的帖子内容在其他地方有些重复,我很抱歉。

我有我的 MainView,其中包含一个菜单项,该菜单项调用命令以显示 RS422ConfigView。在此视图中,我有 8 个 Rs422ConfigControlGroup (UseControls),其中包含 8 个其他控件,除了两个之外,其他控件都是组合框。我无法绑定这些单独的控件,以便可以访问其子控件。我预期的机械化图像如下:enter image description here

我已将依赖项属性添加到我的 Rs422DeviceControlGroup.xaml.cs,如下所示:

namespace EngineeringLabUi.CustomControls
{
    [INotifyPropertyChanged]
    public partial class Rs422DeviceControlGroup : UserControl
    {
        public Rs422DeviceControlGroup()
        {
            InitializeComponent();
        }

        #region UseCheckedDp

        /// Gets or sets the the ischecked property on the use checkbox
        public bool UseChecked
        {
            get { return (bool)GetValue(UseCheckedProperty); }
            set { SetValue(UseCheckedProperty, value); }
        }

        public object UseCheckedDp { get; set; }

        /// Use checkbox dependency property
        public static readonly DependencyProperty UseCheckedProperty =
            DependencyProperty.Register("UseCheckedDp", typeof(bool),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion UseCheckedDp

        #region ReadTypeDp

        /// Gets or sets the the read type property on the use read type combo box
        public SerialOpEnumType? ReadType
        {
            get { return (SerialOpEnumType)GetValue(ReadTypedProperty); }
            set { SetValue(ReadTypedProperty, value); }
        }

        public object ReadTypeDp { get; set; }

        /// Read type combo box dependency property
        public static readonly DependencyProperty ReadTypedProperty =
            DependencyProperty.Register("ReadTypeDp", typeof(SerialOpEnumType),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion ReadTypeDp

        #region WriteTypeDp

        /// Gets or sets the the write type property on the use write type combo box
        public SerialOpEnumType? WriteType
        {
            get { return (SerialOpEnumType)GetValue(WriteTypeProperty); }
            set { SetValue(WriteTypeProperty, value); }
        }

        public object WriteTypeDp { get; set; }

        /// Write type combo box dependency property
        public static readonly DependencyProperty WriteTypeProperty =
            DependencyProperty.Register("WriteTypeDp", typeof(SerialOpEnumType),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion WriteTypeDp

        #region BaudRateDp

        /// Gets or sets the the baud rate  property on the baud rate combo box
        public SerialBaudRatesEnumType? BaudRate
        {
            get { return (SerialBaudRatesEnumType)GetValue(BaudRateProperty); }
            set { SetValue(BaudRateProperty, value); }
        }

        public object BaudRateDp { get; set; }

        /// Baud rate dependency property
        public static readonly DependencyProperty BaudRateProperty =
            DependencyProperty.Register("BaudRateDp", typeof(SerialBaudRatesEnumType),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion BaudRateDp

        #region ParityDp

        /// Gets or sets the the parity property on the parity combo box
        public Parity? Parity
        {
            get { return (Parity)GetValue(ParityProperty); }
            set { SetValue(ParityProperty, value); }
        }

        public object ParityDp { get; set; }

        /// Parity dependency property
        public static readonly DependencyProperty ParityProperty =
            DependencyProperty.Register("BaudRateDp", typeof(Parity),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion ParityDp

        #region DataBitsDp

        /// Gets or sets the the data bits property on the data bits combo box
        public SerialDataBitsEnumType? DataBits
        {
            get { return (SerialDataBitsEnumType)GetValue(DataBitsProperty); }
            set { SetValue(DataBitsProperty, value); }
        }

        public object DataBitsDp { get; set; }

        /// Data bits dependency property
        public static readonly DependencyProperty DataBitsProperty =
            DependencyProperty.Register("DataBitsDp", typeof(SerialDataBitsEnumType),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion DataBitsDp

        #region StopBitsDp

        /// Gets or sets the the parity property on the stop bits combo box
        public StopBits? StopBits
        {
            get { return (StopBits)GetValue(StopBitsProperty); }
            set { SetValue(StopBitsProperty, value); }
        }

        public object StopBitsDp { get; set; }

        /// Stop bits dependency property
        public static readonly DependencyProperty StopBitsProperty =
            DependencyProperty.Register("StopBitsDp", typeof(StopBits),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion StopBitsDp

        #region FccPositionDp

        /// Gets or sets the the parity property on the stop bits combo box
        public FccPositionsEnumType? FccPosition
        {
            get { return (FccPositionsEnumType)GetValue(FccPositionProperty); }
            set { SetValue(FccPositionProperty, value); }
        }

        public object FccPositionDp { get; set; }

        /// Stop bits dependency property
        public static readonly DependencyProperty FccPositionProperty =
            DependencyProperty.Register("FccPositionDp", typeof(FccPositionsEnumType),
                typeof(Rs422DeviceControlGroup), new PropertyMetadata(""));

        #endregion FccPositionDp
    }
}

然后在我的 Rs422ConfigView.cs 中,我尝试绑定到这些依赖项属性:

  x:Class="EngineeringLabUi.Views.Rs422ConfigView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customControls="clr-namespace:EngineeringLabUi.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:EngineeringLabUi"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:EngineeringLabUi.ViewModels"
Title="RS422 Device Configuration"
Width="auto"
Height="auto"
MinWidth="100"
MinHeight="600"
MaxWidth="1300"
MaxHeight="700"
mc:Ignorable="d">
<Window.DataContext>
    <viewModels:Rs422ConfigViewModel />
</Window.DataContext>
<Window.Resources>
    <DataTemplate x:Key="Rs422DeviceDataTemplate">
        <customControls:Rs422DeviceControlGroup />
    </DataTemplate>
</Window.Resources>
<Grid>
    <StackPanel
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Orientation="Vertical">
        <WrapPanel>
            <!--#region Rs422Dev0CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev0CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[0], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev0CtrlGrp-->
            <!--#region Rs422Dev1CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev1CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[1], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev1CtrlGrp-->
            <!--#region Rs422Dev2CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev2CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[2], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev2CtrlGrp-->
            <!--#region Rs422Dev3CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev3CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[3], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev3CtrlGrp-->
            <!--#region Rs422Dev4CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev4CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[4], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev4CtrlGrp-->
            <!--#region Rs422Dev5CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev5CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[5], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev5CtrlGrp-->
            <!--#region Rs422Dev6CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev6CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[6], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev6CtrlGrp-->
            <!--#region Rs422Dev7CtrlGrp-->
            <customControls:Rs422DeviceControlGroup
                x:Name="Rs422Dev7CtrlGrp"
                BaudRateDp="{Binding ModelBaudRate}"
                DataBitsDp="{Binding ModelDataBits}"
                DataContext="{Binding Rs422DeviceColl[7], Mode=OneWay}"
                FccPositionDp="{Binding ModelFccPosition}"
                ParityDp="{Binding ModelParity}"
                ReadTypeDp="{Binding ViewModelReadType}"
                StopBitsDp="{Binding ModelStopBits}"
                UseCheckedDp="{Binding ViewModelUseChecked}"
                WriteTypeDp="{Binding ViewModelWriteType}" />
            <!--#endregion Rs422Dev7CtrlGrp-->
        </WrapPanel>
    </StackPanel>
</Grid>

上述代码中引用的 Rs422DeviceColl[7] 对象位于我的 Rs422ConfigModel.cs 中:

        public Rs422ConfigModel()
    {

    }

    /* Ok so here is a problem...After changing the User class to be called UserModel I can see that the below observable collection for Users are
     * objects of the UseModel class.  In my observable collection I had used the AddUserControl which is the WPF control but instead I should be using the
     * UserControlModel instead which is a C# object
     */
    private static ObservableCollection<Rs422ConfigModel> _rs422ConfigModel = new ObservableCollection<Rs422ConfigModel>();

    public static ObservableCollection<Rs422ConfigModel> GetAddRs422Ctrl()
    {
        return _rs422ConfigModel;
    }

    // an object comes into this function from the AddUserViewModel:AddUserCtrlMethod
    public static void AddRs422Ctrl(Rs422ConfigModel ctrl)
    {
        // This is where it is actually added to the collection
        _rs422ConfigModel.Add(ctrl);
    }

    public bool? ModelUseChecked { get; set; }
    public SerialOpEnumType? ModelReadType { get; set; }
    public SerialOpEnumType? ModelWriteType { get; set; }
    public SerialBaudRatesEnumType? ModelBaudRate { get; set; }
    public Parity? ModelParity { get; set; }
    public SerialDataBitsEnumType? ModelDataBits { get; set; }
    public StopBits? ModelStopBits { get; set; }
    public FccPositionsEnumType? ModelFcccPosition { get; set; }
    public SerialDeviceStateEnumType? ModelState { get; set; }
}

我对何时何地为 UserControls 设置 DataContext 感到困惑。我读到我不应该直接在 UserControl 中设置它们,但所有这些 UserControls 只需要 Rs422ConfigViewModel 的 DataContext。另外,我不确定每个 UserControl 是否应该有自己的 Rs422ConfigViewModel。我也不太确定这是否是一个好问题。

我尝试过一个只有两个窗口的简单项目,但仍然无法弄清楚。我已经注册了 Udemy 课程以更快地启动,但我也没有发现这太有帮助。我希望有人能指出缺失的环节,我希望这篇文章能帮助其他有 C# 经验但没有 MVVM 的人。

我已经研究过的一些资源列表:

C# WPF XAML MVVM

评论

1赞 Clemens 11/13/2023
"我对何时何地为 UserControls 设置 DataContext 感到困惑。- 您永远不会在任何地方显式设置 UserControl 的 DataContext。UserControl 与任何其他控件一样,应该从其父元素继承 DataContext 属性的值,该父元素通常是某种 ContentPresenter 或 ContentControl,而后者又从其父元素继承 DataContext。显式设置 DataContext 的唯一位置是可视化树的根目录,即在 Window 或 Page 中。
0赞 Clemens 11/13/2023
除此之外,目前尚不清楚究竟什么不起作用。请查看 Visual Studio 中的“输出窗口”,了解调试应用程序时可能出现的数据绑定错误消息。
1赞 Clemens 11/13/2023
UserControl 也不需要实现 INotifyPropertyChanged,因为它的依赖项属性已经提供了自己的通知机制。
0赞 Clemens 11/13/2023
而且,您可能希望使用 WrapPanel 作为其 ItemsPanel 的 ItemsControl,而不是在 WrapPanel 中显式声明的 8 个控件。UserControl 将在 ItemsControl 的 ItemTemplate 中声明,从而自动绑定到 ItemsSource 集合的相应项。请参阅数据模板化概述

答: 暂无答案