Slider.Value 在不应该调整时更改最小值

Slider.Value changes on adjusting Minimum when it shouldn#39;t(Slider.Value 在不应该调整时更改最小值)
本文介绍了Slider.Value 在不应该调整时更改最小值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简而言之:
在某些情况下,设置 Slider.Minimum 会调整 Slider.Value,尽管当前值大于新的最小值.

In short:
Under certain circumstances setting Slider.Minimum will adjust Slider.Value although the current Value is bigger then the new Minimum.

代码:(应该是可重现的)
MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <Slider Name="MySlider" DockPanel.Dock="Top" AutoToolTipPlacement="BottomRight" />
        <Button Name="MyButton1" DockPanel.Dock="Top" Content="shrink borders"/>
        <Button Name="MyButton2" DockPanel.Dock="Top" VerticalAlignment="Top" Content="grow borders"/>
    </DockPanel>
</Window>

MainWindow.xaml.cs:

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            MySlider.ValueChanged += (sender, e) =>
            {
                System.Diagnostics.Debug.WriteLine("Value changed: " + e.NewValue);
            };

            System.ComponentModel.DependencyPropertyDescriptor.FromProperty(Slider.MinimumProperty, typeof(Slider)).AddValueChanged(MySlider, delegate
            {
                System.Diagnostics.Debug.WriteLine("Minimum changed: " + MySlider.Minimum);
            });

            System.ComponentModel.DependencyPropertyDescriptor.FromProperty(Slider.MaximumProperty, typeof(Slider)).AddValueChanged(MySlider, delegate
            {
                System.Diagnostics.Debug.WriteLine("Maximum changed: " + MySlider.Maximum);
            });

            MySlider.Value = 1;

            MySlider.Minimum = 0.5;
            MySlider.Maximum = 20;

            MyButton1.Click += (sender, e) =>
            {
                MySlider.Minimum = 1.6;
                MySlider.Maximum = 8;
            };

            MyButton2.Click += (sender, e) =>
            {
                MySlider.Minimum = 0.5;
                MySlider.Maximum = 20;
            };
        }
    }
}

复制步骤:
1. 在调试模式下运行.
2. 将滑块移到最右边.
3.按缩小边框"按钮.
4.按增加边框"按钮.

Steps to reproduce:
1. Run in Debug mode.
2. Move Slider to the far right.
3. Press "shrink borders" Button.
4. Press "grow borders" Button.

预期输出:

Value changed: 1
Minimum changed: 0,5
Maximum changed: 20
//Multiple times "Value changed"
Value changed: 20
Minimum changed: 1,6
Value changed: 8
Maximum changed: 8
Minimum changed: 0,5
Maximum changed: 20

滑块停留在 8.

实际输出:

Value changed: 1
Minimum changed: 0,5
Maximum changed: 20
//Multiple times "Value changed"
Value changed: 20
Minimum changed: 1,6
Value changed: 8
Maximum changed: 8
Value changed: 1
Minimum changed: 0,5
Maximum changed: 20

(请注意额外的值已更改:1")
滑块跳到 1.

(Note the additional "Value changed: 1")
Slider jumps to 1.

旁注:
将 Slider.Value OneWayToSource(或 TwoWay)绑定到双属性,解决了这个问题.

Sidenote:
Binding Slider.Value OneWayToSource (or TwoWay) to a double property, fixes the issue.

问题:
为什么会这样?

Question:
Why is this happening?

理论:
我很确定这与 价值强制 有关.似乎发生了以下情况:
1. 将 Value 设置为 1 以编程方式设置 Value 的基值".它在最小值和最大值的默认值之间,因此有效值"完全相同.
2. 设置最小值和最大值不会改变任何东西,因为值仍在它们之间.
3. 手动将滑块向右拉显然会更改有效值",但由于某些奇怪的原因不会更改基本值".
4. 再次增加边界,调用强制回调,它意识到(错误的)基值"在新的最小值和最大值之间,并将有效值"改回它.

Theory:
I'm pretty sure it has something to do with Value Coercion. It seems like the following happens:
1. Setting Value to 1 programmatical sets Value's "base value". It is between the default values for Minimum and Maximum, so the "effective value" is exactly the same.
2. Setting Minimum and Maximum does not change anything, because Value is still in between them.
3. Manually pulling the Slider to the right apparently changes the "effective value" but for some weird reason not the "base value".
4. Increasing the borders again, calls the coercion callback, which realizes, that the (wrong) "base value" is between the new Minimum and Maximum and changes the "effective value" back to it.

假设这确实是正在发生的事情,我们会提出以下问题:
为什么手动拉动 Slider (3.) 不影响基值"?

Assuming this is indeed what is happening, leads us to the following question:
Why is manually pulling the Slider (3.) not affecting the "base value"?

推荐答案

我不确定用例是什么,但是如果您使用 MVVM 并且一切都已绑定,则可能可以避免此问题.如果这就是您正在做的事情,而这只是您重现问题的另一种方式,那就太好了.

I'm not sure what the use case is, but this issue could probably be avoided if you were using MVVM and everything was bound. If that's what you're doing and this is just another way you're reproducing the issue, good find.

看看 滑块 源代码.我无法查明问题所在,但我更清楚地了解了正在使用的内部值.

Take a look at the Slider source code. I couldn't pinpoint the issue, but I have a better understanding that there is an internal value being used.

添加 2 个按钮,并且只在所有按钮的事件处理程序中更改最小值或最大值:

Add 2 buttons, and only change Minimum or Maximum in the event handlers for all buttons:

MyButton1.Click += (sender, e) =>
{
    MySlider.Minimum = 1.6;
};

MyButton2.Click += (sender, e) =>
{
    MySlider.Minimum = 0.5;
};

MyButton3.Click += (sender, e) =>
{
    MySlider.Maximum = 20;
};

MyButton4.Click += (sender, e) =>
{
    MySlider.Maximum = 8;
};

启动时,点击MyButton1MyButton2:

Value changed: 1.6
Minimum changed: 1.6
Value changed: 1
Minimum changed: 0.5
Value changed: 1.6
Minimum changed: 1.6
Value changed: 1
Minimum changed: 0.5

您可以看到,在内部,它存储原始起始值并在范围能够显示它时恢复它,并将其设置回原来的值.如果您在启动时仅更改最大值(不移动滑块),则 Value 属性不会更改,因为 Value 在当前范围内:

You can see, internally, it stores the original start value and restores it when the range is capable of showing it, it sets it back to what it was. If you change only the maximum's at startup (without moving slider bar), the Value property isn't changed because the Value is in the current range:

Maximum changed: 8
Maximum changed: 20
Maximum changed: 8
Maximum changed: 20

但是,当您将滑块最大值更改为 20,然后更改 Maximum = 8Minimum = 1.6 时,Minimum 为现在超出​​(内部)Value (1) 的范围,并使用 Maximum 值作为它的 Value.当你再次增长它时,你设置 Minimum = 0.5Maximum = 20,并且由于内部值 = 1 并且在 0.5 和 20 之间,它设置了 回到1.

However, when you change the slider bar maximum to 20, then change the Maximum = 8 and Minimum = 1.6, the Minimum is now out of range from (internal) Value (1) and uses the Maximum value for it's Value. When you grow it again, you set Minimum = 0.5 and Maximum = 20, and since the internal value = 1 and is between 0.5 and 20, it sets the Value back to 1.

我找到了一种解决方法,即每次更改范围时都重置内部值.要重置,您只需再次设置 Slider.Value.如果在更改 MaximumMinimum 后执行此操作,则如果旧值不在新范围的范围内,它将保留该值.所以采取你的初步实施:

I found one workaround, and that is resetting the internal value every time you change the range. To reset, you just set the Slider.Value again. If you do this after you change the Maximum and Minimum it will persist the value in the event the old value isn't in the range of the new range. So taking your initial implementation:

MyButton1.Click += (sender, e) =>
{
    MySlider.Minimum = 1.6;
    MySlider.Maximum = 8;
    MySlider.Value = MySlider.Value;
};

MyButton2.Click += (sender, e) =>
{
    MySlider.Minimum = 0.5;
    MySlider.Maximum = 20;
    MySlider.Value = MySlider.Value;
};

输出(符合您的预期):

Value changed: 1
Minimum changed: 0.5
Maximum changed: 20
//Multiple times "Value changed"
Value changed: 20
Minimum changed: 1.6
Value changed: 8
Maximum changed: 8
Minimum changed: 0.5
Maximum changed: 20

编辑

您发布的有关价值强制的链接非常有帮助.我不确定您的问题是否会被视为第二个问题,但我相信我找到了答案.

Edit

The link you posted about value coercion is very helpful. I'm not sure if your question would be considered a second question, but I believe I found the answer.

当您使用 Slider(技术上是 ThumbTrack)时,实现 Thumb 滑动around 通过 Slider 绑定到 Slider.Value.UpdateValue 被调用.现在这个问题是 SetCurrentValueInternal 不是开源的 :( 我们可以得出的结论是神秘函数不是强制 ValueProperty,而是它只是设置基数(desired") 值.尝试:

When you use the Slider (technically the Thumb with the Track), realize that the Thumb sliding around is bound to the Slider.Valuethrough Slider.UpdateValue gets called. This issue now is that SetCurrentValueInternal is not open source :( What we can conclude is that the mystery function isn't coercing ValueProperty, instead it's only setting the base ("desired") value. Try:

private void MyButton1_Click(object sender, RoutedEventArgs e)
{
    MySlider.Minimum = 1.6;
    MySlider.Maximum = 8;
    MySlider.CoerceValue(Slider.ValueProperty); //Set breakpoint, watch MySlider.Value before and after breakpoint.
}

执行上述操作,您会看到即使 Value 将从 20 下降到 8,当您实际强制 ValueProperty 下降到 8 时,该值将变为 1.6.事实上,当你扩大范围时就会发生这种情况.设置 Max 或 Min 后,您将看到 Value 更改,因为它再次强制 value 属性.尝试翻转 Max 和 Min:

Doing the above you will see that even though Value will go from 20 down to 8, the second you actually Coerce ValueProperty when it drops down to 8, the value will then change to 1.6. In fact, that's what happens when you grow the range. After you set Max or Min you will see the Value change since it is coercing value property again. Try flipping Max and Min:

private void MyButton2_Click(object sender, RoutedEventArgs e)
{
    MySlider.Maximum = 20; //Will change Value to 1.6 after executing
    MySlider.Minimum = 0.5; //Will change Value to 1 after executing
}

不过,这让你想,如果可能的话,它怎么能记住回到 1,是否存在有效值、基值和初始值?我不确定.

Still, this makes you think, how can it remember to go back to 1 when possible, is there an effective, base, and initial values? I'm not sure.

事实上试试这个.

  1. 开始申请
  2. 将拇指一直向右移动(值 = 20)
  3. 缩小边框(值 = 8)
  4. 用拇指向左移动,然后一直向右移动(值 = 8)
  5. 然后长出边框

在第 5 步之后,您会观察到 Value 仍然等于 8.这就是我发现它与那个神秘函数有关的原因.

After step 5 you will observe that Value still equals 8. This is how I found out that its something to do with that mystery function.

注意:我的大脑又死了,所以如果不清楚,我很抱歉,我必须回来编辑它.

Note: My brain is dead again, so if this isn't clear, I apologize, I'll have to come back and edit it.

一方面,SetCurrentValueInteralSetValue 都调用 SetValueCommon.不同之处在于 SetCurrentValueInternal 使用基值重新评估当前值,因为强制标志设置为 true.(将值保持在 Min/Max 的范围内),ref.

For one, both SetCurrentValueInteral and SetValue both call SetValueCommon. The difference is SetCurrentValueInternal re-evaluates the current value with the base value since the coerce flag is set to true. (keeps the value in-bounds of Min/Max), ref.

通过我的挖掘,发现SetCurrentValueSetValue有两个完全不同的结果,那就是:指定强制(IsInternal好像在绑定属性是值类型的情况下不使用).

Through my digging, I found that SetCurrentValue and SetValue have two completely different results, and that is: specifying the coercion (IsInternal seems to be unused in the case where the bound property is of value type).

我的证明是 文档 说明:

"SetCurrentValue 方法是另一种设置属性的方法,但它不按优先顺序排列.相反,SetCurrentValue 使您可以更改属性的值,而不会覆盖先前值的来源.您可以使用 SetCurrentValue任何时候你想设置一个值而不赋予该值本地值的优先级..."

"The SetCurrentValue method is another way to set a property, but it is not in the order of precedence. Instead, SetCurrentValue enables you to change the value of a property without overwriting the source of a previous value. You can use SetCurrentValue any time that you want to set a value without giving that value the precedence of a local value..."

话虽如此,移动 SliderThumb 不会影响基值,因为它正在调用 SetCurrentValueInternal.

With that being said, moving the Thumb of the Slider doesn't effect the base value because it is calling SetCurrentValueInternal.

此外,更改 Value 会手动更改基值,因为它调用 SetValue.ValueProperty 依赖属性定义了如何使用 CoerceValueCallback 进行强制,如上一段中所述这里.更详细地了解 Slider.Value 中发生的事情,

Moreover, changing the Value manually changes the base value because it calls SetValue. The ValueProperty dependency property defines how to coerce using the CoerceValueCallback as stated in the last paragraph here. In more detail of what's going on in Slider.Value,

强制与基值交互的方式是应用强制约束,因为当时存在这些约束,但仍保留基值.因此,如果稍后解除强制中的约束,则强制将返回尽可能接近该基值的值,并且一旦解除所有约束,强制对属性的影响可能就会停止.

Coercion interacts with the base value in such a way that the constraints on coercion are applied as those constraints exist at the time, but the base value is still retained. Therefore, if constraints in coercion are later lifted, the coercion will return the closest value possible to that base value, and potentially the coercion influence on a property will cease as soon as all constraints are lifted.

当然我会进一步阅读,依赖属性回调和验证 - 高级强制和回调场景 发现了这个:

Of course I read further, Dependency Property Callbacks and Validation - Advanced Coercion and Callback Scenarios and found this:

例如,在 Min/Max/Current 场景中,您可以选择让用户设置最小值和最大值.如果是这样,您可能需要强制最大值始终大于最小值,反之亦然.但是,如果该强制是活动的,并且最大值强制为最小值,它将使 Current 处于不可设置的状态,因为它依赖于两者并且被限制在值之间的范围内,即零.然后,如果调整了最大值或最小值,电流似乎会跟随"其中一个值,因为电流的期望值仍然被存储,并且随着约束的放松而试图达到期望值.

For instance, in the Min/Max/Current scenario, you could choose to have Minimum and Maximum be user settable. If so, you might need to coerce that Maximum is always greater than Minimum and vice versa. But if that coercion is active, and Maximum coerces to Minimum, it leaves Current in an unsettable state, because it is dependent on both and is constrained to the range between the values, which is zero. Then, if Maximum or Minimum are adjusted, Current will seem to "follow" one of the values, because the desired value of Current is still stored and is attempting to reach the desired value as the constraints are loosened.

总之,我认为这是在 Slider 中设计的,因为它的编码方式是 Value 必须始终介于 Minimum 和 <代码> 最大值.Value 将遵循 Maximum 属性,当解除约束(当前值在基值范围内)时,value 属性将返回其原始值.

In conclusion, I think this is as-designed in Slider because it is coded in such a way that Value must always be in between Minimum and Maximum. The Value will follow the Maximum property, and when constraints are lifted (the current value is in range of the base value) the value property will return to its original value.

最后,IMO WPF 以这种方式设计了滑块,因为 WPF 与数据绑定密切相关.我假设他们的设计假设开发人员将充分利用数据绑定并实现逻辑以防止使用无效值.

Lastly, IMO WPF designed the slider this way because WPF goes hand in hand with data binding. I assume they designed under the assumption that developers would take full advantage of data-binding and would implement the logic to prevent invalid values from being used.

这篇关于Slider.Value 在不应该调整时更改最小值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

DispatcherQueue null when trying to update Ui property in ViewModel(尝试更新ViewModel中的Ui属性时DispatcherQueue为空)
Drawing over all windows on multiple monitors(在多个监视器上绘制所有窗口)
Programmatically show the desktop(以编程方式显示桌面)
c# Generic Setlt;Tgt; implementation to access objects by type(按类型访问对象的C#泛型集实现)
InvalidOperationException When using Context Injection in ASP.Net Core(在ASP.NET核心中使用上下文注入时发生InvalidOperationException)
LINQ many-to-many relationship, how to write a correct WHERE clause?(LINQ多对多关系,如何写一个正确的WHERE子句?)