WPF 中 UserControl 与 MainWindow ViewModel 的绑定依赖关系 属性

Binding Dependency Property of UserControl to MainWindow ViewModel in WPF

请允许我将问题简化为基本模块。

我的项目中有一个 UserControl1。它有一个像这样的文本框:

<TextBox Text="{Binding TextProperty}"/>

在代码隐藏中,我有这样的依赖项 属性:

public string TextProperty
{
    get { return (string)GetValue(TextPropertyProperty); }
    set { SetValue(TextPropertyProperty, value); }
}

public static readonly DependencyProperty TextPropertyProperty = DependencyProperty.Register("TextProperty", typeof(string), typeof(UserControl1), new PropertyMetadata(null));

而 UserControl 的构造函数很简单

public UserControl1()
{
    InitializeComponent();  
    DataContext = this;  
}

在主窗口中,我有这个:

<userctrl:UserControl1 TextProperty="{Binding ABC, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="1" Text="{Binding PQR, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>

现在,在主窗口的视图模型中,我有:

    private string _abc;
    public string ABC
    {
        get { return _abc; }
        set 
        { _abc = "20";
            OnPropertyChanged();
        }
    }

    private string _pqr;

    public string PQR
    {
        get { return _pqr; }
        set
        {
            if(value != _pqr)
            {
                _pqr = value;
                ABC = PQR;
                OnPropertyChanged();
            }
        }
    }

现在,即使是 UserControl 也应该复制我在文本框中写的任何内容,对吗?

但事实并非如此。我在 ABC 的 setter 处设置了断点。它得到更新,但该更新没有到达 UserControl。我的直觉说绑定失败了,这是因为我在 UserControl.

的构造函数中将 DataContext 设置为 this

我该如何解决?

实际情况:

这是用户控件的 XAML:

<UserControl x:Class="MyDiskTools.UserControls.NodeGrid.NodeGrid"
             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:MyDiskTools.UserControls.NodeGrid"             
             mc:Ignorable="d">
    <Grid>
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="Padding" Value="5"/>
                <Setter Property="BorderThickness" Value="1"/>
                <Setter Property="Command" Value="{Binding InputCommand}"/>
                <Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>                
                <Style.Triggers>                    
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="BorderThickness" Value="5"/>
                        <Setter Property="FontSize" Value="20"/>
                        <Setter Property="FontFamily" Value="Times New Roman"/>
                    </Trigger>                    
                </Style.Triggers>
            </Style>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <UniformGrid Grid.Row="0" Rows="1">
            <Button Content="A" />
            <Button Content="B" />
            <Button Content="C" />
            <Button Content="D" />
            <Button Content="E" />
            <Button Content="F" />
        </UniformGrid>
        <UniformGrid Grid.Row="1" Rows="1">
            <Button Content="G" />
            <Button Content="H" />
            <Button Content="I" />
            <Button Content="J" />
            <Button Content="K" />
            <Button Content="L" />
            <Button Content="M" />
        </UniformGrid>
        <UniformGrid Grid.Row="2" Rows="1">
            <Button Content="N" />
            <Button Content="O" />
            <Button Content="P" />
            <Button Content="Q" />
            <Button Content="R" />
            <Button Content="S" />
            <Button Content="T" />
        </UniformGrid>
        <UniformGrid Grid.Row="3" Rows="1">
            <Button Content="U" />
            <Button Content="V" />
            <Button Content="W" />
            <Button Content="X" />
            <Button Content="Y" />
            <Button Content="Z" />
        </UniformGrid>
        <TextBox Name="InputMessage" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" IsEnabled="False" Background="Beige" Grid.Row="4" Text="{Binding PasswordDisplay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</UserControl>

这是代码隐藏:

public partial class NodeGrid : UserControl
{
    public NodeGrid()
    {
        InitializeComponent();            
        InputCommand = new InputCharacterCommand(this);
        DataContext = this;
    }

    public string PasswordDisplay
    {
        get { return (string)GetValue(PasswordDisplayProperty); }
        set { SetValue(PasswordDisplayProperty, value); }
    }


    public static readonly DependencyProperty PasswordDisplayProperty =
        DependencyProperty.Register("PasswordDisplay", typeof(string), typeof(NodeGrid), new PropertyMetadata(""));


    private ICommand _inputCommand;

    public ICommand InputCommand
    {
        get
        {
            return _inputCommand;
        }

        set
        {
            _inputCommand = value;
        }
    }


    public void AddCharacter(string input)
    {
        if (input != null)
        {
            PasswordDisplay = string.Concat(PasswordDisplay, input);
        }
    }

    public bool InputAllowed()
    {
        if (PasswordDisplay == null)
        {
            return true;
        }

        if (PasswordDisplay.Length < 50)
        {
            return true;
        }

        return false;
    }

    private void OnPropertyChange([CallerMemberName] string property = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;

}

class InputCharacterCommand : ICommand
{
    private NodeGrid _vmodel;

    public InputCharacterCommand(NodeGrid vm)
    {
        _vmodel = vm;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return (_vmodel.InputAllowed());
    }

    public void Execute(object parameter)
    {
        _vmodel.AddCharacter(parameter as string);
    }
}

下面是我在主窗口中使用它的方式:

<StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
    <WrapPanel HorizontalAlignment="Center">
        <Label Content="Enter PIN"/>
        <TextBox CommandManager.PreviewCanExecute="HandleCanExecute" Foreground="Transparent" MinWidth="100" Padding="10,0" Text="{Binding PIN, UpdateSourceTrigger=PropertyChanged}"/>
    </WrapPanel>
    <customlock:NodeGrid  MinHeight="250" MinWidth="500" PasswordDisplay="{Binding NodeGridDisplay}"/>
    <Button VerticalAlignment="Center" HorizontalAlignment="Center" Content="Unlock!"/>
</StackPanel>

代码隐藏:

private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    if (e.Command == ApplicationCommands.Cut ||
        e.Command == ApplicationCommands.Copy ||
        e.Command == ApplicationCommands.Paste)
        {
            e.CanExecute = false;
            e.Handled = true;
        }

}

现在,ViewModel:

    private string _PIN;

    public string PIN
    {
        get
        {
            return _PIN;
        }
        set
        {
            if(value != _PIN)
            {
                _PIN = value;                    
                OnPropertyChanged();
                NodeGridDisplay = HashIt(_PIN);
            }
        }
    }

    private string _nodeGridDisplay;

    public string NodeGridDisplay
    {
        get
        {
            return _nodeGridDisplay;
        }

        set
        {
            if (value != _nodeGridDisplay)
            {
                _nodeGridDisplay = value;
                OnPropertyChanged();
            }                
        }
    }

    private string HashIt(string input)
    {
        using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
        {
            return System.Text.Encoding.Default.GetString(md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(input))).GetHashCode().ToString();
        }
    }

预期的功能是什么?

让我举例说明。

The Lock

可以输入 PIN,其哈希值将显示在 NodeGrid 中。然后用户将单击将与哈希连接的字母。然后用户可以点击解锁。

你应该在你的 UserControl 中使用 ElementName 或使用 RaltiveResource 来查找它,你不应该使用 DataContext = this。 (参见 this answer and this link

为您的 userControl 命名并写入:

<UserControl x:Class= ......
      Name="userControl">
     <TextBox Text="{Binding Text, ElementName = userControl}"/>
</UserControl>

请注意,您应该使用 Text 而不是 TextProperty,并确保将 _abc = "20" 更改为 _abc = value