简体   繁体   中英

WPF C# UI update a property from a different class

I have created a simple example to illustrate my problem. I cannot manipulate a property from another class. I created two classes one has a command and a property. The second has just the same command, but referencing the property in the first class.

The class with the property and command works as expected. the other does not, but I don't get why. I am rather new at this and I am obviously missing something conceptually... an explanation would be greatly appreciated.

<Window x:Class="TestProperties.MainWindow"
    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:TestProperties"
    xmlns:mvvm="clr-namespace:TestProperties.MVVM"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525" Background="Black">
<Window.Resources>
    <mvvm:Class1 x:Key="Class1"/>
    <mvvm:Class2 x:Key="Class2"/>
</Window.Resources>

<StackPanel DataContext="{Binding Mode=OneWay, Source={StaticResource Class1}}">

    <TextBox Height="33" Width="100" Margin="0 33 0 0"
             Text="{Binding MyProperty, UpdateSourceTrigger=PropertyChanged}"/>

    <Button Height="33" Width="100" Margin="33" Content="Class 1 - change value" 
            Command="{Binding ChangeValue, Mode=OneWay, Source={StaticResource Class1}}"/>

    <Button Height="33" Width="100" Margin="33" Content="Class 2 - change value" 
            Command="{Binding ChangeValue, Mode=OneWay, Source={StaticResource Class2}}"/>

</StackPanel>

namespace TestProperties.MVVM
{
class Class1 : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
            Debug.WriteLine($"Class1 NOTIFY PROPERTY CHANGED! {info.ToString()}");
        }
    }

    private int myVar;
    public int MyProperty
    {
        get { return myVar; }
        set { myVar = value; NotifyPropertyChanged("MyProperty"); }
    }

    private ICommand _ChangeValue;
    public ICommand ChangeValue
    {
        get
        {
            if (_ChangeValue == null)
            {
                _ChangeValue = new RelayCommand<object> CanExecute_ChangeValue);
            }

            return _ChangeValue;
        }
    }
    public bool CanExecute_ChangeValue(object parameter)
    {
        return true;
    }
    public void Execute_ChangeValue(object parameter)
    {
        Console.WriteLine($"Execute_ChangeValue");

        MyProperty = 5;

        Console.WriteLine($"c1.MyProperty: {MyProperty}");
    }
}

class Class2
{
    private ICommand _ChangeValue;
    public ICommand ChangeValue
    {
        get
        {
            if (_ChangeValue == null)
            {
                _ChangeValue = new RelayCommand<object>(Execute_ChangeValue, CanExecute_ChangeValue);
            }

            return _ChangeValue;
        }
    }
    public bool CanExecute_ChangeValue(object parameter)
    {
        return true;
    }
    public void Execute_ChangeValue(object parameter)
    {
        Console.WriteLine($"Execute_ChangeValue");

        Class1 c1 = new Class1();
        c1.MyProperty = 5;

        Console.WriteLine($"c1.MyProperty: {c1.MyProperty }");
    }
}

public class RelayCommand<T> : ICommand
{
    #region Fields
    readonly Action<T> _execute = null;

    readonly Predicate<T> _canExecute = null;
    #endregion // Fields

    #region Constructors
    public RelayCommand(Action<T> execute) : this(execute, null)
    {

    }

    public RelayCommand(Action<T> execute, Predicate<T> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members
    //[DebuggerStepThrough]

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute((T)parameter);
    }


    public event EventHandler CanExecuteChanged
    {
        add
        { CommandManager.RequerySuggested += value; }

        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }
    #endregion // ICommand Members
}

You are changing the value in a brand new instance of the class. An instance the rest of the code has no idea exists, and so of course has no way to observe the new property value. The command ought to be using the Class1 object you've declared in XAML, not a newly-created instance of Class1 .

It's hard to know what would be the best solution in your real-world code. But, in the code you posted, given that you seem to want to declare the instances of these classes in XAML, you could change Class2 to have a property that can be set to the instance of Class1 you care about, and then use that instance in the command:

class Class2
{
    private ICommand _ChangeValue;
    public ICommand ChangeValue
    {
        get
        {
            if (_ChangeValue == null)
            {
                _ChangeValue = new RelayCommand<object>(Execute_ChangeValue, CanExecute_ChangeValue);
            }

            return _ChangeValue;
        }
    }

    // NOTE: not observable, due to lack of INotifyPropertyChanged.
    // It's fine in this scenario, but don't expect to be able to change
    // this value and have bindings update automatically.
    private Class1 _class1;
    public Class1 Class1
    {
        get { return _class1; }
        set { _class1 = value; }
    }

    public bool CanExecute_ChangeValue(object parameter)
    {
        return true;
    }

    public void Execute_ChangeValue(object parameter)
    {
        Console.WriteLine($"Execute_ChangeValue");

        Class1.MyProperty = 5;

        Console.WriteLine($"Class1.MyProperty: {Class1.MyProperty }");
    }
}

Then in the XAML, something like this (note that all Class1 references become relative to Class2 ):

<Window.Resources>
    <mvvm:Class2 x:Key="Class2">
        <mvvm:Class2.Class1>
            <mvvm:Class1 x:Key="Class1"/>
        </mvvm:Class2.Class1>
    </mvvm:Class2>
</Window.Resources>

<StackPanel DataContext="{Binding Class1, Mode=OneWay, Source={StaticResource Class2}}">

    <TextBox Height="33" Width="100" Margin="0 33 0 0"
             Text="{Binding MyProperty, UpdateSourceTrigger=PropertyChanged}"/>

    <Button Height="33" Width="100" Margin="33" Content="Class 1 - change value" 
            Command="{Binding Class1.ChangeValue, Mode=OneWay, Source={StaticResource Class2}}"/>

    <Button Height="33" Width="100" Margin="33" Content="Class 2 - change value" 
            Command="{Binding ChangeValue, Mode=OneWay, Source={StaticResource Class2}}"/>

</StackPanel>

In your real code, you may prefer some other means to share the Class1 reference with the Class2 object. Or, don't duplicate the command in the first place. Frankly, it seems a little odd to me that there would be two different commands implemented to both do the same thing. There's not enough context in the question to know whether that's really needed and, if not, what one would change to fix it. But it's certainly something you should double-check to make sure you really need to do that.

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