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

Tim*_*ann 8 c# wpf slider

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

代码:(应该是可重现的)
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>
Run Code Online (Sandbox Code Playgroud)

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;
            };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

重现步骤:
1.在调试模式下运行.
2.将滑块移动到最右侧.
3.按"收缩边框"按钮.
4.按"增长边框"按钮.

预期产出:

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
Run Code Online (Sandbox Code Playgroud)

滑块停留在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
Run Code Online (Sandbox Code Playgroud)

(注意附加的"值已更改:1")
滑块跳转到1.

旁注:将
Slider.Value OneWayToSource(或TwoWay)绑定到double属性,修复了该问题.

问题:
为什么会这样?

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

假设确实发生了这种情况,请引导我们提出以下问题:
为什么手动拉动滑块(3.)不会影响"基值"?

Kcv*_*vin 6

我不确定用例是什么,但是如果你使用MVVM并且一切都受到限制,这个问题可能会被避免.如果这就是你正在做的事情,这只是你再现问题的另一种方式,那就是好的发现.

看一下Slider源代码.我无法确定问题,但我更清楚地知道有一个内部价值被使用.

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

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;
};
Run Code Online (Sandbox Code Playgroud)

在启动时,单击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
Run Code Online (Sandbox Code Playgroud)

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

Maximum changed: 8
Maximum changed: 20
Maximum changed: 8
Maximum changed: 20
Run Code Online (Sandbox Code Playgroud)

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

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

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;
};
Run Code Online (Sandbox Code Playgroud)

输出(符合您的预期):

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
Run Code Online (Sandbox Code Playgroud)

编辑

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

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

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.
}
Run Code Online (Sandbox Code Playgroud)

执行上述操作后,您将看到即使Value从20下降到8,第二个实际上Coerce ValueProperty下降到8时,该值也会变为1.6.事实上,当你增加范围时会发生这种情况.设置最大值或最小值后,您将看到值更改,因为它再次强制值属性.尝试翻转Max和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
}
Run Code Online (Sandbox Code Playgroud)

不过,这让你想到,如果可能的话,它怎么能记得回到1,是否有一个有效的,基础的和初始的价值观?我不确定.

事实就是这样.

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

在第5步之后,你会发现Value仍然等于8.这就是我发现它与神秘功能有关的方法.

注意:我的大脑已经死了,所以如果不清楚,我道歉,我将不得不回来编辑它.

编辑2

一个,两个SetCurrentValueInteralSetValue两个都打电话SetValueCommon.SetCurrentValueInternal由于coerce标志设置为true,因此差异是使用基值重新计算当前值.(保持最小/最大值的入口),参考.

通过我的挖掘,我发现SetCurrentValue并且SetValue有两个完全不同的结果,那就是:指定强制(IsInternal在绑定属性为值类型的情况下似乎未使用).

我的证据是文件说明:

"SetCurrentValue方法是设置属性的另一种方法,但它不是优先顺序.相反,SetCurrentValue允许您更改属性的值而不覆盖先前值的源.您可以随时使用SetCurrentValue你想设置一个值,而不给该值优先于本地值......"

有了这样说,移动ThumbSlider,因为它是调用不会影响基础值SetCurrentValueInternal.

此外,更改Value手动会因为调用而更改基值SetValue.该ValueProperty依赖项属性定义了使用如何强制CoerceValueCallback在最后一段说这里.更详细地了解发生了什么Slider.Value,

强制与基值相互作用,使得强制约束在当时存在约束时被应用,但基值仍然保留.因此,如果后来解除强制约束,强制将返回可能与该基值相关的最接近的值,并且一旦所有约束被解除,潜在的对财产的强制影响将停止.

当然,我进一步阅读,依赖属性回调和验证 - 高级强制和回调方案,并发现:

例如,在最小/最大/当前场景中,您可以选择将最小值和最大值设置为用户.如果是这样,您可能需要强制最大值始终大于最小值,反之亦然.但是如果该强制是有效的,并且最大强制为最小值,则它将电流置于不可设置的状态,因为它依赖于两者并且被约束到值之间的范围,即零.然后,如果调整最大值或最小值,则电流似乎"跟随"其中一个值,因为仍然存储了期望的电流值,并且在约束松开时尝试达到所需的值.

总之,我认为这是作为设计的Slider,因为它是在这样一种方式,编码Value必须始终之间MinimumMaximum.在Value将按照Maximum属性,并且当约束被解除(当前值在基准值范围)的值属性将返回到其原始值.

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