Slider.Value在调整最小值时会发生变化,因为它不应该

时间:2015-08-17 14:43:11

标签: 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>

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.按&#34;收缩边框&#34;按钮。
4.按&#34;增长边界&#34;按钮。

预期输出:

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

(注意附加&#34;值已更改:1&#34;)
滑块跳到1。

旁注:
将Slider.Value OneWayToSource(或TwoWay)绑定到double属性可以解决问题。

问题:
为什么会这样?

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

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

1 个答案:

答案 0 :(得分:6)

我不确定用例是什么,但是如果您使用MVVM并且所有内容都已绑定,则可能会避免此问题。如果这就是您正在做的事情,这只是您重现问题的另一种方式,那就是好的发现。

看看Slider source code。我无法确定问题,但我更清楚地了解到正在使用内部价值。

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

在启动时,点击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位于当前范围内:

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

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

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

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

修改

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

当您使用Slider(技术上ThumbTrack)时,请注意Thumb左右滑动绑定到Slider.Value到{被调用{3}}。现在这个问题是SetCurrentValueInternal不是开源的:(我们可以得出结论,神秘功能不是强制ValueProperty,而是它只设置基础(&# 34;期望&#34;)值。尝试:

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,第二个实际上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
}

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

事实很重要。试试这个。

  1. 启动申请
  2. 将拇指一直向右移动(值= 20)
  3. 收缩边框(值= 8)
  4. 用拇指向左移动,然后向右移动(值= 8)
  5. 然后成长边框
  6. 在第5步之后,你会发现Value仍然等于8.这就是我发现它与神秘功能有关的方法。

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

    编辑2

    首先,SetCurrentValueInteralSetValue都会调用SetValueCommon。由于coerce标志设置为true,因此差异为SetCurrentValueInternal使用基值重新计算当前值。 (保持最小值/最大值的入口值),Slider.UpdateValue

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

    我的证据是ref表明:

      

    &#34; SetCurrentValue方法是另一种设置属性的方法,但它不是优先顺序。相反,SetCurrentValue使您可以更改属性的值,而不会覆盖先前值的源。您可以在任何时候使用SetCurrentValue设置值而不将该值赋予本地值的优先级...&#34;

    话虽如此,移动Thumb的{​​{1}}并不会影响基值,因为它正在调用Slider

    此外,更改SetCurrentValueInternal会手动更改基值,因为它会调用ValueSetValue依赖项属性定义如何使用ValueProperty强制执行,如上一段documentation中所述。更详细地了解CoerceValueCallback中的内容,

      

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

    当然,我进一步阅读here并发现了这一点:

      

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

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

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