文本框双向绑定到WPF中的滑块

时间:2012-12-28 20:17:24

标签: wpf data-binding slider

我正在努力找到解决滑块和文本框上数据绑定问题的方法。

设置: 滑块的当前值显示在文本框内。当用户拖动滑块时,该值会反映在文本框内。用户可以选择拖动滑块并释放到他选择的值,单击滑块轨道上的任意位置以设置值或在texbox中手动输入值。在最后一种情况下,在文本框中输入的值应更新滑块位置。

texbox是双向绑定到datacontext属性,而滑块是绑定到同一属性的一种方式。当用户滑动或单击滑块跟踪器时,我使用滑块的dragcompleted事件来通知datacontext修改。另一方面,当用户点击跟踪器时,我使用滑块的OnValueChanged事件来通知datacontext(并使用一个标志来确保滑块移动不触发OnValueChanged)

问题:即使使用绑定值初始化滑块值,OnFalueChanged事件也会触发,因此我无法确定该值是否实际来自用户或绑定。

请问您可以建议可能的替代方法来进行绑定,以确保我们可以区分滑块的用户更新和绑定udpates吗? 哎呀!

更新抱歉,我忘了提及为什么我没有直接绑定滑块和文本框两种方式,如下面的答案所示。对数据上下文值的更新应该触发对后端服务器的调用并从数据库中检索数据。问题是,当用户拖动滑块时,它会不断触发更新。我只是依靠实际的onValueChanged事件来调用DoWhatever方法来解决这个问题。我希望这更清楚一点。很抱歉省略这个...

我快速将下面的示例放在一起为您试一试。

xaml

<Window x:Class="SliderIssue.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">
<Grid HorizontalAlignment="Center"
      VerticalAlignment="Center">
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <Slider Name="slider" VerticalAlignment="Top"  
            ValueChanged="slider_ValueChanged"
            Thumb.DragStarted="slider_DragStarted"
            Thumb.DragCompleted="slider_DragCompleted"
            Value="{Binding Count}"
            Width="200"
            Minimum="0"
            Maximum="100"/>
    <TextBox VerticalAlignment="Top" 
             HorizontalAlignment="Left"
             Grid.Column="1" 
             Width="100" 
             Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" 
             Height="25"/>    
</Grid>

背后的代码:

using System.Windows;

namespace SliderIssue
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private bool _dragStarted;

        public MainWindow()
        {
            InitializeComponent();

            var item = new Item();
            DataContext = item;
        }

        private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (!_dragStarted)
            {
                var item = (Item)DataContext;
                item.DoWhatever(e.NewValue);
            }
        }

        private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
        {
            _dragStarted = true;
        }

        private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
        {
            _dragStarted = false;

            var item = (Item) DataContext;
            item.DoWhatever(slider.Value);
        }
    }
}

一个简单的数据类:

using System.ComponentModel;

namespace SliderIssue
{
    public class Item : INotifyPropertyChanged
    {
        private int _count = 50;
        public int Count
        {
            get { return _count; }
            set
            {
                if (_count != value)
                {
                    _count = value;
                    DoWhatever(_count);
                    OnPropertyChanged("Count");
                }
            }
        }

        public void DoWhatever(double value)
        {
            //do something with value
            //and blablabla
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

2 个答案:

答案 0 :(得分:2)

更新:

与Eric一致,但作为单独的操作建议。

  1. 将两个控件绑定到Count,如下所示。
  2. 创建一个计时器,每秒检查两个变量。
  3. (计时器检查#1)检查数据库请求是否正在进行(例如布尔标志)。如果是真的,它什么都不做。如果没有操作(false),则转到步骤4.
  4. (计时器检查#2)检查计数是否改变。如果count已更改,则设置数据请求进行标记(在步骤3中找到/使用)并启动异步数据库调用并退出。
  5. (数据库操作调用)获取数据库数据并相应地更新VM。它将数据请求进行标志设置为false,这允许定时器检查在计数更改时启动新请求。
  6. 这样即使用户对滑块感到疯狂,您也可以管理更新。


    我相信你可能已经过度思考过了。从滑块和文本框中删除所有事件。如果第一个值(以编程方式设置)不应该调用您的DoWhatever方法,那么请检查该代码以跳过第一次初始化....

    我建议您将滑块绑定到Count作为TwoWay模式,并让Count属性执行您需要的其他过程(如实体类所示)。无需检查点击或任何其他事件。如果用户更改了文本框中的值,则会更改滑块,反之亦然。

    <Slider Name="slider"
            VerticalAlignment="Top"
            Value="{Binding Count, Mode=TwoWay}"
            Width="200"
            Minimum="0"
            Maximum="100" />
    <TextBox VerticalAlignment="Top"
             HorizontalAlignment="Left"
             Grid.Column="1"
             Width="100"
             Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
             Height="25" />
    

答案 1 :(得分:1)

<强>更新

好的,现在我明白你为什么要那样做了。我有几点建议可能会有所帮助。

我的第一个有点自以为是,但我仍然提供它。如果您尝试解决的问题是限制对后端数据库的请求,我会认为您的ViewModel不需要关注它。我会根据从ViewModel传递下来的更新值,将一个层向下推入一个对后端进行调用的对象。

每次调用查询后端数据库的方法时,都可以通过记录DateTimeOffset.Now来创建穷人的限制尝试。将该值与记录的最后一个值进行比较。如果之间的TimeSpan低于最低阈值,请更新它所比较的​​值,并忽略该请求。

您可以使用计时器执行类似的操作,并在每次发出请求时重置计时器,但这样更麻烦。

当调用从后端返回时,该层会引发一个ViewModel处理的事件,并对返回的数据执行任何操作。

作为另一个建议,我还会查看ReactiveExtensions给你的内容。需要花一些时间来处理大脑的工作方式,但是您可以从事件流中创建Observable,然后使用Throttle()方法返回另一个Observable。您订阅Observable并在那里进行通话。它需要更多地重新思考软件的设计和架构,但这很有趣。

Paul Betts基于Rx创建了一个名为ReactiveUI的完整MVVM框架。我首先在他的一篇博文here中了解到了对Observables的限制。

祝你好运!

原始帖子

如果我正确理解您的问题,听起来您希望Slider和TextBox都反映DataContext的相同属性(通常是ViewModel)。看起来你正试图复制WPF的绑定机制给你的东西。我能够得到这个工作的快速原型。这是我使用的代码。

对于视图,我刚创建了一个新窗口,其中包含Window的内容。

<StackPanel>
  <Slider Value="{Binding TheValue}" Margin="16" />
  <TextBox Text="{Binding TheValue}" Margin="16" />
</StackPanel>

请注意,Slider和TextBox都绑定到DataContext的相同(巧妙命名)值。当用户在TextBox中输入新值时,该值将更改,并且属性更改通知(在ViewModel中)将使滑块自动更新其值。

以下是ViewModel的代码(即View的DataContext)。

class TextySlideyViewModel : ViewModelBase
{
  private double _theValue;

  public double TheValue
  {
    get { return _theValue; }
    set
    {
      if(_theValue == value)
        return;

      _theValue = value;
      OnPropertyChanged("TheValue");
    }
  }
}

我的ViewModel派生自一个实现INotifyPropertyChanged接口的ViewModelBase类。 OnPropertyChanged()方法在基类中定义,它只引发名称作为参数传递的属性的事件。

最后,我创建了View并将ViewModel的一个新实例分配为DataContext(我直接在App的OnStartup()方法中为此示例执行了此操作。)

我希望这有助于您朝着正确的方向前进。