4

我最难找到一种方法来解决我在滑块和文本框上的数据绑定问题。

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

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

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

您能否建议进行绑定的替代方法,以确保我们可以区分滑块的用户更新和绑定更新?谢谢你!

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

我快速整理了下面的示例供您尝试。

xml

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

2 回答 2

2

更新

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

我的第一个比较固执己见,但我还是提供了它。如果您要解决的问题是限制对后端数据库的请求,我认为您的 ViewModel 不需要关注它。我会将它向下推到一个对象中,该对象根据从 ViewModel 向下传递的更新值调用后端。

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

您可以使用计时器做类似的事情,并在每次发出请求时重置计时器,但这更麻烦。

当调用从后端返回时,该层会引发 ViewModel 处理的事件,并对返回的数据执行它需要做的任何事情。

作为另一个建议,我还会查看ReactiveExtensions为您提供的内容。您需要花点时间了解它们是如何工作的,但是您可以Observable从事件流中创建一个,然后使用该Throttle()方法返回另一个Observable. 您订阅它Observable并在那里执行您的呼叫。这需要更多地重新思考软件的设计和架构,但这很有趣。

Paul Betts 基于 Rx 创建了一个完整的 MVVM 框架,称为ReactiveUI。我是在他的一篇博文中第一次了解到限制 Observables

祝你好运!

原帖

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

对于视图,我刚刚创建了一个新窗口,以此作为窗口的内容。

<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(我在OnStartup()本示例的 App 方法中直接执行了此操作)。

我希望这可以帮助您朝着正确的方向前进。

于 2012-12-28T21:16:25.647 回答
2

更新:

与 Eric 类似,但作为单独的操作建议。

  1. 正如我在下面建议的那样,将两个控件绑定到 Count 作为两种方式。
  2. 创建一个计时器,它每秒触发一次,检查两个变量。
  3. (Timer Check #1) 检查数据库请求是否正在进行(例如布尔标志)。如果它是真的,它什么也不做。如果没有操作(假),则转到步骤 4。
  4. (Timer Check #2) 检查计数是否改变。如果计数已更改,它会设置数据请求正在进行标志(如在步骤 3 中找到/使用的那样)并启动异步数据库调用并退出。
  5. (数据库操作调用)获取数据库数据并相应地更新 VM。它将数据请求正在进行标志设置为 false,这允许计时器检查在计数更改时启动新请求。

这样,即使用户对滑块发疯,您也可以管理更新。


我相信你可能想多了。从滑块和文本框中删除所有事件。如果第一个值(以编程方式设置)不应调用您的 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" />
于 2012-12-28T21:13:00.270 回答