【问题标题】:Binding Dependency Property of UserControl to MainWindow ViewModel in WPF在 WPF 中将 UserControl 的依赖属性绑定到 MainWindow ViewModel
【发布时间】:2017-07-25 17:00:45
【问题描述】:

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

我的项目中有一个 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}"/>

现在,在 MainWindow 的 viewModel 中,我有:

    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

我该如何解决?

实际情况:

这是 UserControl 的 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);
    }
}

这是我在 MainWindow 中使用它的方式:

<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 中。然后用户将单击将与哈希连接的字母。然后用户可以点击解锁。

【问题讨论】:

    标签: wpf data-binding user-controls viewmodel dependency-properties


    【解决方案1】:

    你应该在你的UserControl 中使用ElementName 或者使用RaltiveResource 来找到它,你不应该使用DataContext = this。 (见this answerthis link

    给你的 userControl 起个名字然后写:

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

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

    【讨论】:

    • 抱歉_abc = "20"。我正在使用它来查看它是否适用于恒定值。
    • 可能我给出的问题描述太简单了。我的情况有点复杂。我已经编辑了我的问题以说明我的特定问题。
    • 与您的简单示例相同。在这部分使用ElementName Text="{Binding PasswordDisplay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"。其他的似乎还好。
    • 非常感谢!这个问题困扰我太久了。不知何故使用 ElementName 似乎不起作用,也许我做错了什么,但使用 RelativeSource 就可以了!
    猜你喜欢
    • 2014-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-19
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多