c# - slider 值在更改最小值和最大值后不更新

标签 c# wpf xaml mvvm

我在回答 this问题,在这样做的同时我发现了很多奇怪的行为。因为我是 MVVM 的拥护者,所以我整理了一个解决方案以查看我是否会看到相同的行为。我的解决方案发现,即使我将 TwoWay 绑定(bind)到 Slider.Value,它也不会在 Slider.Maximum 之后在我的 ViewModel 中更新> 和 Slider.Minimum 改变;即我的 View 模型的 Value 可以在 UpperLimitLowerLimit 之外,同时 Slider.Value(我的 VM 的 Value 属性绑定(bind)到)在范围内。

在上述问题中,更改 Slider.MaximumSlider.Minimum 似乎始终使 Slider.Value 保持在范围内,有时“将”Slider.Value 恢复到以前设置的值。

Microsoft's Slider Source Code

  1. 为什么 Slider.Value 会更改/恢复其在链接问题中看到的值,即使当前值在最小/最大范围内?
  2. 为什么 我的 View 模型的 Value 属性绑定(bind)到 Slider.ValueTwoWay 不匹配更改 UpperLimitLowerLimit 后绑定(bind)?
    • 请注意,Maximum 和 Minimum 的绑定(bind)起作用

主窗口.xaml:

<DockPanel>
    <Slider Name="MySlider" DockPanel.Dock="Top" AutoToolTipPlacement="BottomRight" Value="{Binding Value, Mode=TwoWay}" Maximum="{Binding UpperLimit}" Minimum="{Binding LowerLimit}"/>
    <Button Name="MyButton1" Click="MyButton1_Click" DockPanel.Dock="Top" Content="shrink borders"/>
    <Button Name="MyButton2" Click="MyButton2_Click" DockPanel.Dock="Top" VerticalAlignment="Top" Content="grow borders"/>
    <Button Name="MyButton3" Click="MyButton3_Click" DockPanel.Dock="Top" VerticalAlignment="Top" Content="Print ItemVM Value"/>
</DockPanel>

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    private readonly ItemViewModel item;
    public MainWindow()
    {
        InitializeComponent();
        DataContext = item = new ItemViewModel(new Item(1, 20, 0.5));
    }

    private void MyButton1_Click(object sender, RoutedEventArgs e)
    {
        //MySlider.Minimum = 1.6;
        //MySlider.Maximum = 8;
        item.LowerLimit = 1.6;
        item.UpperLimit = 8;

    }

    private void MyButton2_Click(object sender, RoutedEventArgs e)
    {
        //MySlider.Minimum = 0.5;
        //MySlider.Maximum = 20;
        item.LowerLimit = 0.5;
        item.UpperLimit = 20;
    }

    private void MyButton3_Click(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Item Value: " + item.Value);
        System.Diagnostics.Debug.WriteLine("Slider Value: " + MySlider.Value);
    }
}

项目/项目 View 模型:

public class ItemViewModel : INotifyPropertyChanged
{
    private readonly Item _item;

    public event PropertyChangedEventHandler PropertyChanged;

    public ItemViewModel(Item item)
    {
        _item = item;
    }

    public double UpperLimit
    {
        get
        {
            return _item.UpperLimit;
        }
        set
        {
            _item.UpperLimit = value;
            NotifyPropertyChanged();
        }
    }
    public double LowerLimit
    {
        get
        {
            return _item.LowerLimit;
        }
        set
        {
            _item.LowerLimit = value;
            NotifyPropertyChanged();
        }
    }

    public double Value
    {
        get
        {
            return _item.Value;
        }
        set
        {
            _item.Value = value;
            NotifyPropertyChanged();
        }
    }

    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
public class Item
{
    private double _value;
    private double _upperLimit;
    private double _lowerLimit;
    public double Value
    {
        get
        {
            return _value;
        }
        set
        {
            _value = value;
        }
    }
    public double UpperLimit
    {
        get
        {
            return _upperLimit;
        }
        set
        {
            _upperLimit = value;
        }
    }
    public double LowerLimit
    {
        get
        {
            return _lowerLimit;
        }
        set
        {
            _lowerLimit = value;
        }
    }

    public Item(double value, double upperLimit, double lowerLimit)
    {
        _value = value;
        _upperLimit = upperLimit;
        _lowerLimit = lowerLimit;
    }
}

Steps to reproduce:

  1. Click MyButton3

    • Item Value = 1

    • Slider Value = 1

  2. Move Slider/Thumb all the way to right

  3. Click MyButton3

    • Item Value = 20

    • Slider Value = 20

  4. Click MyButton1

  5. Click MyButton3

    • Item Value = 20

    • Slider Value = 8

如果在MyButton3_Click中打断点,执行最后一步,可以看到MySlider.Value = 8

最佳答案

这是由于值强制,可以read more about it here .

一般来说,WPF 控件设计用于松散数据绑定(bind)。添加了它们的获取/设置访问器和事件等以协助从 Winforms 进行转换,但它们添加了一个额外的逻辑层,该逻辑层并不总是过滤到您的绑定(bind)属性。这是将“好的”WPF 代码(数据绑定(bind))与“坏的”代码(直接访问控件)混合使用时可能出现的众多问题示例之一。

编辑:

每当需要确定当前值时,都会调用依赖属性的强制回调处理程序。将其视为修改结果的最后机会;它不会改变绑定(bind)本身,只会改变返回值。如果您在包含值 10 的 View 模型(例如)中有一个整数属性,并且您将文本框绑定(bind)到它,如下所示:

    <TextBlock Text="{Binding MyValue}" />

这个值显然会显示为 10。现在假设您创建了一个用户控件,它有一个名为“MyProperty”的整数依赖属性,并且假设强制回调将当前值乘以 2:

    <local:MyControl x:Name="myControl" MyProperty="{Binding MyValue}" />

这不会做任何事情。我们将 MyProperty 绑定(bind)到 MyValue 属性,但它只是一个 DP。我们从来没有真正调用过它。现在假设我们添加第二个 TextBox 但这次绑定(bind)到 MyControl.MyProperty:

    <TextBlock Text="{Binding Path=MyProperty, ElementName=myControl}" />

第一个控件将继续显示 10(该值仍在我们的 View 模型中),但第二个将显示 20,因为对 MyProperty DP 的强制调用修改了它从自己绑定(bind)到 MyValue 中获得的值。 (有趣的是,双向绑定(bind)也有效,强制回调会导致值在更改时翻倍)。

所有这一切的重要线索是,强制回调仅在需要通过另一个依赖更新自身或通过手动调用 getter 的代码来解析值时调用。显然,您在 Slider 上调用 Value getter 会导致这种情况发生,但仅更改 Minimum 和 Maximum 的值不会。这就像在不调用属性更改通知的情况下更改 View 模型中的属性值...您知道自己做了什么,但没有别的。

进一步阅读:the RangeBase source code (特别是 ConstrainToRange 强制回调)和 the Slider source code (即仅在拖动 slider 或缩略图时调用的 UpdateValue)。

关于c# - slider 值在更改最小值和最大值后不更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32060975/

相关文章:

c# - 如何将 XAML 代码的动画转换为 C#

c# - 必须实现通用的小于和大于操作

wpf - 在 WPF 数据网格中的列之间禁用制表位

c# - 使用反射获取所有属性并在方法中传递每个属性

c# - wpf 边框比标签大得多

c# - 在开头引号旁边使用大括号时出现 HeaderStringFormat 问题

C#角色服装系统

c# - WPF 命令如何获取所有必要的信息以添加到正确的列表项

c# - 更新面板中的图像导致整个页面刷新

c# - 使用值转换器将切换按钮绑定(bind)到枚举