wpf WPF中文本框到滑块的两种方式绑定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14075255/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 06:48:29  来源:igfitidea点击:

Two way binding of a textbox to a slider in WPF

wpfdata-bindingslider

提问by ak3nat0n

I am having the hardest time figuring out a way to solve a problem I am having with databinding on a slider and a textbox.

我最难想出一种方法来解决我在滑块和文本框上进行数据绑定时遇到的问题。

The setup: the current value of the slider is displayed inside of the textbox. When the user drags the slider the value is reflected inside the textbox. The user can choose to drag the slider and release to the value he chooses, click anywhere on the slider track to set the value or enter the value manually in the texbox. In the last case, the value entered in the textbox should update the slider position.

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

The texbox is two way bound to a datacontext property while the slider is one way bound to the same property. When the user slides or click on the slider tracker, I use the dragcompleted event of the slider to notify the datacontext of the modification. When the user clicks on the tracker on the other hand I use the OnValueChanged event of the slider to notify the datacontext (and use a flag to ensure the OnValueChanged was not triggered by a slider move)

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

The problem: The OnValueChanged event fires even when initializing the slider value with the binding value so I cannot figure out whether the value is actually coming from the user or the binding.

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

Could you please suggest maybe and alternative way to do the binding to ensure we can distinguish between user update and binding udpates for the slider? Thnak you!

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

UPDATESorry I forgot to mention why I am not binding directly both slider and textbox two ways like the below answers suggest. The update to the data context value is supposed to trigger a call to a backend server and retrieve data from a database. The problem is that when the user drags the slider it constantly fires updates. I go around the problem by only relying to the actual onValueChanged event to call the DoWhatever method. I hope that's a bit clearer. Sorry for omitting this...

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

I quickly put together the sample below for you to give it a try.

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

The xaml:

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>

The code behind:

背后的代码:

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

A simple data class:

一个简单的数据类:

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

采纳答案by Eric Olsson

UPDATE

OK, now I see why you were trying to do it like that. I have a couple of suggestions that may help.

更新

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

My first one is a bit more opinionated, but I offer it nonetheless. If the problem you are trying to solve is throttling requests to a back-end database, I would contend that your ViewModel need not concern itself with that. I would push that down a layer into an object that is making the call to the back-end based on the updated value passed down from the ViewModel.

我的第一个有点固执己见,但我仍然提供它。如果您试图解决的问题是限制对后端数据库的请求,我认为您的 ViewModel 不需要关心它。我会将其向下推入一个对象,该对象根据从 ViewModel 传递的更新值调用后端。

You could create a poor-man's throttling attempt by recording DateTimeOffset.Noweach time a call is made to the method to query the back-end DB. Compare that value to the last value recorded. If the TimeSpan between falls beneath your minimum threshold, update the value to which it was compared, and ignore the request.

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

You could do a similar thing with a timer and resetting the timer each time a request is made, but that is messier.

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

When the call returns from the back-end, this layer raises an event which the ViewModel handles and does whatever it needs to do with the data returned.

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

As another suggestion, I would also check out what the ReactiveExtensionsgive you. It takes a bit to kind of wrap your brain around how they work, but you could create an Observablefrom a stream of events, and then use the Throttle()method to return another Observable. You subscribe to that Observableand perform your call there. It would take more re-thinking the design and architecture of your software, but it is intriguing.

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

Paul Betts created an entire MVVM framework based around Rx called ReactiveUI. I first learned about throttling Observables in one of his blog posts here.

Paul Betts 基于 Rx 创建了一个完整的 MVVM 框架,称为ReactiveUI。我第一次知道在他的博客文章的一个节流观测量这里

Good luck!

祝你好运!

ORIGINAL POST

原帖

If I understand your problem correctly, it sounds like you would like both the Slider and the TextBox to reflect the same property of the DataContext (normally, a ViewModel). It looks like you are trying to duplicate what the binding mechanism of WPF gives you. I was able to get a quick prototype of this working. Here's the code I used.

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

For the view, I just created a new window with this as the content of the Window.

对于视图,我刚刚创建了一个新窗口,并将其作为 Window 的内容。

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

Notice that both the Slider and the TextBox are bound to the same (cleverly-named) value of the DataContext. When the user enters a new value into the TextBox, the value will change, and the property change notification (in the ViewModel) will cause the slider to update its value automatically.

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

Here is the code for the ViewModel (i.e., the DataContext of the View).

这是ViewModel(即View的DataContext)的代码。

class TextySlideyViewModel : ViewModelBase
{
  private double _theValue;

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

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

My ViewModel is derived from a ViewModelBase class which implements the INotifyPropertyChangedinterface. The OnPropertyChanged()method is defined in the base class which merely raises the event for the property whose name is passed as the parameter.

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

Lastly, I created the View and assigned a new instance of the ViewModel as its DataContext (I did this directly in the App's OnStartup()method for this example).

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

I hope this helps get you going in the right direction.

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

回答by ΩmegaMan

UPDATE:

更新:

Along the lines with Eric, but as a separate suggestion of operation.

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

  1. Bind both controls to Count as two way as I suggested below.
  2. Create a timer which fires off every second that checks two variables.
  3. (Timer Check #1) Checks to see if a database request is ongoing (such as a Boolean flag). If it is true, it does nothing. If there is no operation (false), it goes to step 4.
  4. (Timer Check #2) It checks to see if count is changed. If count has changed it sets the data request ongoing flag (as found/used in step 3) and initiates an async database call and exits.
  5. (Database Action Call) Gets the database data and updates the VM accordingly. It sets the data request ongoing flag to false which allows the timer check to start a new request if count is changed.
  1. 按照我在下面建议的两种方式将两个控件绑定到 Count。
  2. 创建一个计时器,它每秒触发一次,检查两个变量。
  3. (Timer Check #1) 检查数据库请求是否正在进行(例如布尔标志)。如果是真的,它什么都不做。如果没有操作(false),则进入步骤4。
  4. (定时器检查#2)它检查计数是否改变。如果计数已更改,它会设置数据请求正在进行的标志(如在步骤 3 中找到/使用的)并启动异步数据库调用并退出。
  5. (数据库操作调用)获取数据库数据并相应地更新 VM。它将数据请求正在进行标志设置为 false,这允许计时器检查在计数更改时启动新请求。

That way you can manage the updates even if a user goes crazy with the slider.

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



I believe you may have over thought this. Remove all the events off of the slider and the textbox. If the first value (set programmatically) should not call your DoWhatever method, then put in a check in that code to skip the first initialization....

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

I recommend that you make the slider bind to Count as a TwoWay mode and have the Count Property do the other process you need (as shown on your entity class). No need to check for clicks or any other event. If the user changes the value in the textbox it changes the slider and visa versa.

我建议您将滑块绑定到 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" />