4

スライダーとテキストボックスでのデータバインディングで発生している問題を解決する方法を見つけるのに最も苦労しています。

設定:スライダーの現在の値がテキストボックス内に表示されます。ユーザーがスライダーをドラッグすると、値がテキストボックス内に反映されます。ユーザーは、スライダーをドラッグして選択した値まで離すか、スライダートラックの任意の場所をクリックして値を設定するか、texboxに手動で値を入力するかを選択できます。最後のケースでは、テキストボックスに入力された値によってスライダーの位置が更新されます。

texboxはdatacontextプロパティに双方向でバインドされていますが、スライダーは同じプロパティに一方向でバインドされています。ユーザーがスライダートラッカーをスライドまたはクリックすると、スライダーのdragcompletedイベントを使用して、変更をデータコンテキストに通知します。一方、ユーザーがトラッカーをクリックすると、スライダーのOnValueChangedイベントを使用してデータコンテキストに通知します(フラグを使用して、OnValueChangedがスライダーの移動によってトリガーされないようにします)

問題:スライダー値をバインディング値で初期化してもOnValueChangedイベントが発生するため、値が実際にユーザーからのものかバインディングからのものかがわかりません。

スライダーのユーザー更新とバインド更新を確実に区別できるように、バインドを行うための代替方法を提案していただけますか?やめろ!

更新申し訳ありませんが、以下の回答が示すように、スライダーとテキストボックスの両方を直接バインドしない理由を2つの方法で説明するのを忘れました。データコンテキスト値の更新は、バックエンドサーバーへの呼び出しをトリガーし、データベースからデータを取得することになっています。問題は、ユーザーがスライダーをドラッグすると、常に更新が発生することです。DoWhateverメソッドを呼び出すために実際のonValueChangedイベントのみに依存することで、問題を回避します。それがもう少し明確になることを願っています。これを省略してすみません...

以下のサンプルを簡単にまとめて、試してみてください。

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

2 に答える 2

2

アップデート:

エリックとの線に沿って、しかし操作の別の提案として。

  1. 以下で提案するように、両方のコントロールを双方向としてカウントするようにバインドします。
  2. 2つの変数をチェックするタイマーを毎秒起動するように作成します。
  3. (タイマーチェック#1)データベース要求が進行中であるかどうかをチェックします(ブールフラグなど)。それが本当なら、それは何もしません。操作がない場合(false)は、手順4に進みます。
  4. (タイマーチェック#2)カウントが変更されているかどうかをチェックします。countが変更された場合は、データ要求の進行中フラグ(ステップ3で検出/使用)を設定し、非同期データベース呼び出しを開始して終了します。
  5. (データベースアクション呼び出し)データベースデータを取得し、それに応じてVMを更新します。データ要求進行中フラグをfalseに設定します。これにより、カウントが変更された場合にタイマーチェックで新しい要求を開始できます。

そうすれば、ユーザーがスライダーに夢中になっている場合でも、更新を管理できます。


あなたはこれを考えすぎたかもしれないと私は信じています。スライダーとテキストボックスからすべてのイベントを削除します。最初の値(プログラムで設定)がDoWhateverメソッドを呼び出さない場合は、そのコードにチェックを入れて、最初の初期化をスキップします。

スライダーをTwoWayモードとしてCountにバインドし、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 に答える
2

更新

OK、今あなたがそのようにそれをやろうとしていた理由がわかります。役立つかもしれないいくつかの提案があります。

私の最初のものはもう少し意見がありますが、それでも私はそれを提供します。解決しようとしている問題がバックエンドデータベースへのリクエストの抑制である場合、ViewModelはそれ自体に関係する必要はないと私は主張します。ViewModelから渡された更新された値に基づいて、バックエンドを呼び出しているオブジェクトにレイヤーをプッシュします。

DateTimeOffset.NowバックエンドDBにクエリを実行するメソッドが呼び出されるたびに記録することで、貧乏人のスロットルの試みを作成できます。その値を最後に記録された値と比較します。間のTimeSpanが最小しきい値を下回った場合は、比較対象の値を更新し、要求を無視します。

タイマーを使って同様のことを行い、リクエストが行われるたびにタイマーをリセットすることもできますが、それは面倒です。

呼び出しがバックエンドから戻ると、このレイヤーはViewModelが処理するイベントを発生させ、返されたデータに対して必要な処理を実行します。

別の提案として、ReactiveExtensionsが提供するものも確認します。それらがどのように機能するかについて頭を悩ませるには少し時間がかかりますがObservable、イベントのストリームからを作成し、そのThrottle()メソッドを使用して別のを返すことができますObservable。あなたはそれを購読し、Observableそこであなたの電話をかけます。ソフトウェアの設計とアーキテクチャを再考する必要がありますが、興味をそそられます。

Paul Bettsは、 ReactiveUIと呼ばれるRxをベースにしたMVVMフレームワーク全体を作成しました。私は最初に、彼のブログ投稿の1つで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(つまり、ビューのDataContext)のコードです。

class TextySlideyViewModel : ViewModelBase
{
  private double _theValue;

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

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

私のViewModelは、インターフェイスを実装するViewModelBaseクラスから派生していINotifyPropertyChangedます。このOnPropertyChanged()メソッドは、名前がパラメーターとして渡されたプロパティのイベントを発生させるだけの基本クラスで定義されます。

最後に、ビューを作成し、そのDataContextとしてViewModelの新しいインスタンスを割り当てました(OnStartup()この例では、アプリのメソッドで直接これを行いました)。

これがあなたを正しい方向に導くのに役立つことを願っています。

于 2012-12-28T21:16:25.647 に答える