【问题标题】:Slider Value does not update after changing Minimum and Maximum更改最小值和最大值后滑块值不更新
【发布时间】:2015-11-10 17:09:40
【问题描述】:

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

在上述问题中,更改Slider.MaximumSlider.Minimum 似乎总是将Slider.Value 保持在范围内,有时会将Slider.Value“恢复”为之前设置的值。

Microsoft's Slider Source Code

  1. 为什么即使当前值在最小/最大范围内,Slider.Value 也会更改/恢复其值,如链接问题中所示?
  2. 为什么在更改UpperLimitLowerLimit 后,绑定到Slider.Value 的视图模型的Value 属性与TwoWay 绑定不匹配?
    • 请注意,与最大值和最小值的绑定确实有效

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

Item/ItemViewModel:

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

重现步骤:

  1. 点击MyButton3

    • 物品价值 = 1

    • 滑块值 = 1

  2. 将滑块/拇指一直向右移动

  3. 点击MyButton3

    • 物品价值 = 20

    • 滑块值 = 20

  4. 点击MyButton1

  5. 点击MyButton3

    • 物品价值 = 20

    • 滑块值 = 8

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

【问题讨论】:

    标签: c# wpf xaml mvvm


    【解决方案1】:

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

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

    编辑:

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

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

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

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

    这将无济于事。我们将 MyProperty 绑定到 MyValue 属性,但它只是一个 DP。我们从来没有真正调用它。现在假设我们添加了第二个 TextBox,但这次绑定到 MyControl.MyProperty:

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

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

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

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

    【讨论】:

    • 我能够将其分解为大部分,但我无法确定故障所在。您是否清楚代码中的行为改变值 - 隐藏和绑定有何不同?这不适合我。我还质疑强制是否会冒泡或隧道下降的事实。当我无法单步执行代码时,当我试图弄清楚 SelectionStart/End 是否在 Value 和 Max/Min 之前被强制执行时,我的思考过程变成了意大利面条。我将不得不阅读有关强制的内容。如果您对如何弄清楚逻辑或正在发生的事情有更多的了解,我会全神贯注。谢谢!
    • 相应地进行了编辑。 :)
    • @MarkFeldman 我查看了 WPF 源代码并设置最小值清楚地调用最大值和值的强制回调。我是不是误解了你的帖子?
    • @MarkFeldman欢迎来到 2015 年。referencesource.microsoft.com/#PresentationFramework/src/… 可能是比您的答案更可靠的链接。我现在将审查您的回答和蒂姆的问题。
    • @Tim:这无疑是在推动我在该领域的知识极限,但据我所知,在已设置的属性上调用 coercevalue 不会调用属性更改通知。重现这一点很容易……绑定两个 DP,一个强制另一个;第二个的绑定没有更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-24
    • 2012-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多