WPF 中用户控件的输入和输出

Input to and Output from User Control in WPF

我制作了这个简约项目来学习用户控制的输出和输入,它按预期工作。我想问一下,这是一个好方法还是有一些不必要的东西? 我也想 post 这个,因为有大量 post 特定的用户案例,但没有一个简单的示例来学习绑定机制。

主要Window:

<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OutputFromUserControl.View"
        xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
        xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
        mc:Ignorable="d"
        Title="Output From User Control" Height="450" Width="800">

    <Window.DataContext>
        <vm:MainVM x:Name="MainVM"/>
    </Window.DataContext>

    <StackPanel HorizontalAlignment="Left">
        <Label Content="Form elements:"/>
        <Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
            <Grid HorizontalAlignment="Left" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto"/>
                    <ColumnDefinition Width="auto"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>

                <Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
                <TextBox Grid.Row="0" Grid.Column="1" 
                     Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
                     Width="200"
                     />
                <Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
                <TextBox Grid.Row="1" Grid.Column="1" 
                     Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
                     Width="200"
                     />
                <Label Content="Name Output from Control: " Grid.Row="2" Grid.Column="0"/>
                <TextBlock Grid.Row="2" Grid.Column="1" 
                     Text="{Binding FullName}"
                     Width="200"
                     />
            </Grid>
        </Border>
        <Label Content="User Control:" Margin="0,10,0,0"/>
        <Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
            <uc:NameConcatControl x:Name="NameUC"
                                  NameInput="{Binding NameInput}" 
                                  SurnameInput="{Binding SurnameInput}"
                                  NameOutput="{Binding FullName, Mode=TwoWay}"
                                  />
        </Border>
    </StackPanel>
</Window>

主虚拟机:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace OutputFromUserControl.ViewModel
{
    public class MainVM : INotifyPropertyChanged
    {
        private string nameInput;

        public string NameInput {
            get { return nameInput; }
            set 
            {
                nameInput = value;
                OnPropertyChanged(nameof(NameInput));
            }
        }

        private string surnameInput;

        public string SurnameInput {
            get { return surnameInput; }
            set {
                surnameInput = value;
                OnPropertyChanged(nameof(SurnameInput));
            }
        }

        private string fullName;

        public string FullName {
            get { return fullName; }
            set {
                fullName = value;
                OnPropertyChanged(nameof(FullName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

对照xaml:

<UserControl x:Class="OutputFromUserControl.View.Controls.NameConcatControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:OutputFromUserControl.View.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>

        <Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
        <TextBlock Grid.Row="0" Grid.Column="1" 
                   Text="{Binding NameInput}"
                   x:Name="NameInputTextBlock"
                   />
        <Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
        <TextBlock Grid.Row="1" Grid.Column="1" 
                   Text="{Binding SurnameInput}"
                   x:Name="SurnameInputTextBlock"
                   />
        <Label Content="Name Output: " Grid.Row="2" Grid.Column="0"/>
        <TextBlock Grid.Row="2" Grid.Column="1" 
                   Text="{Binding NameOutput}"
                   x:Name="OutputNameTextBlock"
                   />
    </Grid>
</UserControl>

用户控件.cs:

using System.Windows;
using System.Windows.Controls;

namespace OutputFromUserControl.View.Controls
{
    /// <summary>
    /// Interaction logic for NameConcatControl.xaml
    /// </summary>
    public partial class NameConcatControl : UserControl
    {
        public string NameInput {
            get { return (string)GetValue(NameInputProperty); }
            set { SetValue(NameInputProperty, value); }
        }

        public static string defaultNameInput = "NameInput";
        public static readonly DependencyProperty NameInputProperty =
            DependencyProperty.Register("NameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameInput, SetNameOutput));


        public string SurnameInput {
            get { return (string)GetValue(SurnameInputProperty); }
            set { SetValue(SurnameInputProperty, value); }
        }

        public static string defaultSurnameInput = "Surname Input";
        public static readonly DependencyProperty SurnameInputProperty =
            DependencyProperty.Register("SurnameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultSurnameInput, SetNameOutput));


        public string NameOutput {
            get { return (string)GetValue(NameOutputProperty); }
            set { SetValue(NameOutputProperty, value); }
        }

        public static string defaultNameOutput = "Name Output";
        public static readonly DependencyProperty NameOutputProperty =
            DependencyProperty.Register("NameOutput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameOutput));


        private static void SetNameOutput(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            NameConcatControl control = (NameConcatControl)d;

            string nameInput = "";
            string surnameInput = "";

            if(e.Property.Name == "NameInput")
            {
                string newValue = (string)e.NewValue;
                nameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
            }
            else
            {
                nameInput = string.IsNullOrEmpty(control.NameInputTextBlock.Text)
                ? ""
                : control.NameInputTextBlock.Text;
            }

            if(e.Property.Name == "SurnameInput")
            {
                string newValue = (string)e.NewValue;
                surnameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
            }
            else
            {
                surnameInput = string.IsNullOrEmpty(control.SurnameInputTextBlock.Text)
                ? ""
                : control.SurnameInputTextBlock.Text;
            }

            string fullName = $"{nameInput} {surnameInput}";

            control.OutputNameTextBlock.Text = fullName;
            control.NameOutput = fullName;
        }

        public NameConcatControl()
        {
            InitializeComponent();
        }
    }
}

这个问题的答案很广泛。不同的人有不同的方法可以用于他们的应用程序。

但我们总是遵循一个共同的公式。 每个视图 - 都有自己的视图模型。 (同样在这种方法中,有人可能会说可能并非一直如此)。

根据您的代码(xaml 和代码),以下是我的观察结果。

<Window.DataContext>
    <vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
  1. 我通常不喜欢在 xaml 中设置数据上下文。相反,我更喜欢将它设置在代码隐藏(主要来自构造函数)

  2. 而不是在用户控件中创建依赖属性并将 MainVM 属性绑定到用户控件的依赖属性。

我更喜欢这样做。

我更喜欢创建一个单独的 UserControlViewModel.cs 并向其中添加所需的属性。

public class UserControlViewModel : INotifyPropertyChanged
{
  private string nameInput;

    public string NameInput {
        get { return nameInput; }
        set 
        {
            nameInput = value;
            OnPropertyChanged(nameof(NameInput));
        }
    }

    private string surnameInput;

    public string SurnameInput {
        get { return surnameInput; }
        set {
            surnameInput = value;
            OnPropertyChanged(nameof(SurnameInput));
        }
    }

    private string fullName;

    public string FullName {
        get { return fullName; }
        set {
            fullName = value;
            OnPropertyChanged(nameof(FullName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

然后我更喜欢将其作为 属性 添加到 MainVM.cs

public class MainVM : INotifyPropertyChanged
{
   private UserControlViewModel _userControlViewModel;

    public UserControlViewModel UserControlViewModel
    {
        get { return _userControlViewModel; }
        set 
        {
            _userControlViewModel = value;
            OnPropertyChanged(nameof(UserControlViewModel));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    // Rest of your code
    // You don't need existing properties any more here.
   // If you want to access these properties from MainVM then use the UserControlViewModel property and access the members of it.
}

然后我更喜欢将我的 UserControl 的数据上下文设置为此 属性,如下面的 MainWindow.xaml

 <uc:NameConcatControl x:Name="NameUC" ="{Binding UserControlViewModel}" />

我的用户控件控件绑定仍然保持不变,因为 属性 名称相同,我们移至 UserControlViewModel.cs

现在您可以从 UserControl.xaml.cs

的代码后面删除所有依赖属性

注意:- 正如我在回答开头所说的那样,这个问题的答案范围很广,并且有很多可能性可以回答这个问题。

我希望我已经尽力为您提供了一些意见。我想这应该会给你一些发展休息的想法..

您可以尝试进行这些更改,如果您遇到任何错误或绑定问题,请告诉我。

假设您只希望全名视图类似于 "Surname, Name",您实际上可以从视图模型中删除 FullName 属性,并且只需使用 MultiBinding(顺便说一句,StringFormat 属性 可以与 MultiBindings 和常规 Bindings 一起使用,如果您不熟悉它,它会非常漂亮)。

至于标签,最好养成使用完成工作所需的最简单控件的习惯,在这种情况下,TextBlocks 就可以了,因为您似乎没有使用任何Label 提供的扩展功能(即 BorderBrush、Padding、ContentTemplate 等)。

您通常不需要在派生的 UserControl 中创建自己的依赖属性 类,因为它们通常在设计时考虑了特定的视图模型。当视图独立于视图模型时,它们更有用,并且依赖属性充当 api,其他 controls/viewmodels 可以通过它与之交互。

<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:OutputFromUserControl.View"
    xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
    xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
    mc:Ignorable="d"
    Title="Output From User Control" Height="450" Width="800">

<Window.DataContext>
    <vm:MainVM x:Name="MainVM"/>
</Window.DataContext>

<StackPanel HorizontalAlignment="Left">
    <Label Content="Form elements:"/>
    <Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
        <Grid HorizontalAlignment="Left" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>

            <TextBlock Text="Name Input:" Grid.Row="0" Grid.Column="0"/>
            <TextBox Grid.Row="0" Grid.Column="1" 
                 Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
                 Width="200"
                 />
            <TextBlock Text="Surname Input:" Grid.Row="1" Grid.Column="0"/>
            <TextBox Grid.Row="1" Grid.Column="1" 
                 Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
                 Width="200"
                 />
            <TextBlock Text="Name Output from Control:" Grid.Row="2" Grid.Column="0"/>
            <TextBlock Grid.Row="2" Grid.Column="1" Width="200">
                <MultiBinding StringFormat="{}{0}, {1}">
                    <Binding Path="SurnameInput"/>
                    <Binding Path="NameInput"/>
                </MultiBinding>
            </TextBlock>
        </Grid>
    </Border>
    <Label Content="User Control:" Margin="0,10,0,0"/>
    <Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
        <uc:NameConcatControl x:Name="NameUC"
                              NameInput="{Binding NameInput}" 
                              SurnameInput="{Binding SurnameInput}"
                              NameOutput="{Binding FullName}"
                              />
    </Border>
</StackPanel>