INotifyPropertyChanged 绑定未按预期更新

发布于 2024-12-12 08:28:53 字数 2997 浏览 0 评论 0原文

所以这就是我要解决的问题:我有一个自定义用户控件,它公开了绑定到我的 ViewModel 的两个依赖属性。在我的 ViewModel 中,我有一个类的实例,该类包含多个属性,这些属性表示与用户控件以及控件操作的项目相关的值。这里有一些示例代码可以直观地解释它,所以这是我的控件的一个简单示例,它是一个滑块,与一个允许用户锁定滑块的复选框相结合。

    <custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />

IsLocked 和 SliderValue 是依赖属性,可有效操作自定义控件中包含的复选框和滑块。除了与我定义的类的绑定之外,所有控制函数都按预期工作。如果我创建单独的属性,例如在一个 int 属性和一个 bool 属性中,绑定将按预期工作。但是,我有五个滑块,实际代码中的每个滑块都有五个与其关联的属性。我试图通过创建一个类来将这些属性保存在可重用对象中,从而将 25 个属性缩减为 5 个类实例,从而消除代码重复。

我的 CustomClass 继承 ObservableObject 并具有分别名为 IsLocked 和 SliderValue 的 bool 属性和 int 属性。对于更多视觉帮助,这里是它的样子:

    public class CustomClass : ObservableObject
    {
        public const string SliderValuePropertyName = "SliderValue";
        private int _sliderValue= 0;
        public int SliderValue
        {
            get
            {
                return _sliderValue;
            }

            set
            {
                if (_sliderValue== value)
                {
                    return;
                }


                _sliderValue= value;
                RaisePropertyChanged(SliderValuePropertyName );
            }
        }

    public const string IsCheckedPropertyName = "IsChecked";
    private bool _isChecked = false;
    public bool IsChecked
    {
        get
        {
            return _isChecked;
        }

        set
        {
            if (_isChecked == value)
            {
                return;
            }
            _isChecked = value;
            RaisePropertyChanged(IsCheckedPropertyName);
        }
    }

ViewModel 属性非常相似,看起来像这样,当 ViewModel 加载时创建该类的一个新实例:

    public const string SliderOnePropertyName = "SliderOne";
    private CustomClass _sliderOne;
    public CustomClass SliderOne
    {
        get
        {
            return _sliderOne;
        }

        set
        {
            if (_sliderOne== value)
            {
                return;
            }

            _sliderOne= value;
            RaisePropertyChanged(SliderOnePropertyName );
        }
    }

Why won't the update of the dependency property that is bound to the类中的属性正确更新吗?是否是因为您无法自行正确更新类实例属性,而必须在发生更改时更新整个类实例?或者我是否需要进一步自定义此 ViewModel 属性中的设置器?现在,更改滑块值或复选框根本不会触及绑定属性,并且在调试时不会出现任何错误。

编辑:我还将该控件包围在 Border 中,并将 Border UIElement 的 DataContext 设置为该类的 DataContext,然后将更简单的路径绑定应用到底层自定义控件。然而,这对我的问题没有任何影响。

我是一名本土程序员,所以在将代码放在一起时我经常会错过一些东西,我猜这就是这种情况,除非我正在尝试的东西不起作用。

任何帮助将不胜感激。

编辑:所以我一直在尝试使用自定义事件,该事件会让我知道自定义控件的特定属性何时发生变化,然后将该事件连接到我的 ViewModel 中以更新现有类。这可以工作,但仍然会产生代码重复,因为现在我必须有 10 个事件,每个控件 2 个事件,一个用于检查滑块值何时更改,另一个用于检测复选框 IsChecked 值何时更改。由于您无法路由多个命令参数(例如正在操纵滑块的简单字符串标识符以及要在代码中使用的值),因此存在此代码重复。此限制意味着我不能只使用 2 个事件来区分定义的方法中哪个控件正在发生更改,因为将物理控件暴露给 ViewModel 会破坏 MVVM 模式。使用类作为用户控件的数据上下文使得我不关心正在操纵什么控件,因为它们每个都有自己的类实例。使用事件可以解开 MVVM 模式,因为现在我需要知道用户正在操纵五个控件中的哪一个。

在属性绑定中使用类并不是那么困难。我必须缺少一些补救措施。

So here is the problem I'm beating my head against: I have a custom user control that exposes two dependency properties that are bound to my ViewModel. In my ViewModel I have an instance of a class that holds multiple properties that express values that relate to the user control as well as to items that control manipulates. Here's a bit of sample code to explain it visually so here is a simple sample of my control, it's a Slider that is combined with a checkbox that allows the user to lock the slider.

    <custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />

IsLocked and SliderValue are dependency properties that effectively manipulate the checkbox and slider that are contained in the custom control. All of the control functions work as intended, except for the bindings to the class I've defined. If I create individual properties, as in one int property and one bool property the bindings work as intended. However I have five sliders, and each slider in my actual code has five properties that tie in to them. I'm trying to eliminate code duplication by creating a class to hold these properties in a reusable object shrinking my 25 properties down to 5 class instances.

My CustomClass inherits ObservableObject and has a bool property and int property named IsLocked and SliderValue respectively. For more visual aids here is what it looks like:

    public class CustomClass : ObservableObject
    {
        public const string SliderValuePropertyName = "SliderValue";
        private int _sliderValue= 0;
        public int SliderValue
        {
            get
            {
                return _sliderValue;
            }

            set
            {
                if (_sliderValue== value)
                {
                    return;
                }


                _sliderValue= value;
                RaisePropertyChanged(SliderValuePropertyName );
            }
        }

    public const string IsCheckedPropertyName = "IsChecked";
    private bool _isChecked = false;
    public bool IsChecked
    {
        get
        {
            return _isChecked;
        }

        set
        {
            if (_isChecked == value)
            {
                return;
            }
            _isChecked = value;
            RaisePropertyChanged(IsCheckedPropertyName);
        }
    }

The ViewModel property is very similar and looks like this, an new instance of the class is created when the ViewModel loads:

    public const string SliderOnePropertyName = "SliderOne";
    private CustomClass _sliderOne;
    public CustomClass SliderOne
    {
        get
        {
            return _sliderOne;
        }

        set
        {
            if (_sliderOne== value)
            {
                return;
            }

            _sliderOne= value;
            RaisePropertyChanged(SliderOnePropertyName );
        }
    }

Why won't the updating of the dependency property that is bound to the property in the class update properly? Is it because you can't properly update the class instance property by itself and instead have to update the entire class instance whenever changes occur? Or do I need to further customize the setter in this ViewModel property? As it sits now changing the slider value or checkbox never hits the bound property at all and nothing errors out when debugging.

EDIT: I've also surrounded the control in a Border and set the Border UIElement's DataContext to that of the class and then subsequently applied the more simple path binding to the underlying custom control. This however did not have any effect on my problem.

I'm a homegrown programmer so I often miss things when putting code together and I'm guessing this is the case here, unless what I'm trying just won't work.

Any help would be greatly appreciated.

EDIT: So I've been toying around with using a custom event that will let me know when the specific property of the custom control changes and then having that event wired up in my ViewModel to update the existing class. This works but still creates code duplication as now I have to have 10 events, 2 events per control, one to check for when the value of the slider changes and the other to detect when the checkbox IsChecked value changes. This code duplication exists since you can't route multiple command parameters (like a simple string identifier for which slider is being manipulated as well as the value you want to use in the code). This limitation means I can't just use 2 events that differentiate between which control is undergoing changes within the defined method as exposing the physical control to the ViewModel breaks the MVVM pattern. Using a class as the datacontext for the user control made it so I didn't care what control was being manipulated as they each had their own class instance. Using events this unravels the MVVM pattern as now I need to know which of the five controls is being manipulated by the user.

It can't be this hard to use a class in property bindings. I have to be missing something remedial.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

花辞树 2024-12-19 08:28:53

这是一个完整的示例:

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.DataContext = new ViewModel();
    }
}


public class ViewModel
{
    public SliderValues slv { get; private set; }

    public ViewModel()
    {
        slv = new SliderValues();
    }
}

public class SliderValues : INotifyPropertyChanged
{
    bool _isLocked = false;

    public bool IsLocked
    {
        get { return _isLocked; }
        set
        {
            _isLocked = value;
            OnPropertyChanged("IsLocked");
        }

    }
    int _theValue = 5;

    public int TheValue
    {
        get { return _theValue; }
        set
        {
            _theValue = value;
            OnPropertyChanged("TheValue");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

    }
}

现在是 xaml:

<UserControl x:Class="TestBindings.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
                DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
    </Grid>
</UserControl>

here is a full example:

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.DataContext = new ViewModel();
    }
}


public class ViewModel
{
    public SliderValues slv { get; private set; }

    public ViewModel()
    {
        slv = new SliderValues();
    }
}

public class SliderValues : INotifyPropertyChanged
{
    bool _isLocked = false;

    public bool IsLocked
    {
        get { return _isLocked; }
        set
        {
            _isLocked = value;
            OnPropertyChanged("IsLocked");
        }

    }
    int _theValue = 5;

    public int TheValue
    {
        get { return _theValue; }
        set
        {
            _theValue = value;
            OnPropertyChanged("TheValue");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

    }
}

Now the xaml:

<UserControl x:Class="TestBindings.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
                DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
    </Grid>
</UserControl>
寄风 2024-12-19 08:28:53

可能只是存在语法错误。试试这个

{Binding Path=CustomClass.IsLocked, Mode=TwoWay}

May be there is just a syntactical error. Try this

{Binding Path=CustomClass.IsLocked, Mode=TwoWay}

无名指的心愿 2024-12-19 08:28:53

试试这个...

Try this...<custom:SliderControl DataContext="{Binding CustomClass}" IsLocked="{Binding IsLocked, Mode=TwoWay}" SliderValue="{Binding Value, Mode=TwoWay}" />

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文