简体   繁体   中英

Binding Dependency Property of UserControl to MainWindow ViewModel in WPF

Allow me to simplify the problem to its basic blocks.

I have a UserControl1 in my project. It has a TextBox like this:

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

In the code-behind, I have a dependency property like this:

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));

And the constructor of the UserControl is simply

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

In the MainWindow, I have this:

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

Now, in the viewModel of the MainWindow, I have:

    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();
            }
        }
    }

Now, even the UserControl is supposed to replicate whatever I write in the textbox, right?

But it doesn't. I have set breakpoints at setter of ABC. It gets updated, but that update does not reach the UserControl. My gut says that the binding is failing silently, and it's because I have set the DataContext to this in the constructor of UserControl.

How am I supposed to solve it?

The Actual Scenario:

Here is the XAML for the UserControl:

<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>

Here is the code-behind:

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);
    }
}

And here is how I use it in my 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>

The Code-behind:

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;
        }

}

And now, the 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();
        }
    }

What is the intended function?

Let me illustrate it.

The Lock

One can enter the PIN, the hash of which will be displayed in the NodeGrid. Then the user will click the letters which will be concatenated with the hash. Then the user can click unlock.

You should use ElementName in your UserControl or use RaltiveResource to find it, and you should not use DataContext = this. (see this answer and this link )

Give your userControl a name and write:

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

Please note that you should use Text instead of TextProperty and make sure to change _abc = "20" to _abc = value .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM