WPF:ユーザーがドラッグした後にトリガするイベントとスライダー
質問
私は現在、WPFでMP3プレーヤーを作っています、と私は、ユーザーが左または右にスライダーをスライドさせて、MP3の特定の位置をシークすることができますスライダーを作りたい。
私はValueChanged
イベントを使用して試してみましたが、それはそれは価値だたびにユーザーがドラッグを終了したときの私は火災にイベントをしたい、変更されたので、あなたがそれを越えドラッグすると、イベントが複数回発生しますトリガその後、スライダーとはの新しい値を取得します。
どのように私はこれを達成することができますか?
<時間>の [更新] の
私は発見しました基本的には同じことを説明し、彼らは二つの「ソリューション」を思い付いたMSDNのこのポストする。どちらかのスライダーをサブクラスまたはタイムスパン後にアクションを起動DispatcherTimer
イベントでValueChanged
を呼び出します。
あなたは上記の2よりも良い事を思い付くことができますか?
解決
あなたはこのために、親指の「DragCompleted」イベントを使用することができます。残念ながら、ドラッグ時にこれだけ発射されるので、あなたは別に、他のクリックやキーの押下を処理する必要があります。あなただけそれがドラッグ可能になりたい場合は、falseにFocusableの0へとLargeChangeを設定することで、スライダーを移動し、これらの手段を無効にすることができます。
例:
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
他のヒント
Thumb.DragCompleted
イベントを使用した以外にも、あなたはまた、ユーザーは、矢印キーを押すか、スライダバーをクリックして値を変更するときに機能を失うことはありません。この方法をValueChanged
とThumb.DragStarted
の両方を使用することができます。
XAMLます:
<Slider ValueChanged="Slider_ValueChanged"
Thumb.DragStarted="Slider_DragStarted"
Thumb.DragCompleted="Slider_DragCompleted"/>
の後ろのコード:
private bool dragStarted = false;
private void Slider_DragCompleted(object sender, DragCompletedEventArgs e)
{
DoWork(((Slider)sender).Value);
this.dragStarted = false;
}
private void Slider_DragStarted(object sender, DragStartedEventArgs e)
{
this.dragStarted = true;
}
private void Slider_ValueChanged(
object sender,
RoutedPropertyChangedEventArgs<double> e)
{
if (!dragStarted)
DoWork(e.NewValue);
}
<Slider PreviewMouseUp="MySlider_DragCompleted" />
私のために動作します。
あなたが望む値はmousupイベントの後、どちらかの側のクリックにしたり、ハンドルのドラッグ後の値です。
のMouseUpはトンネルダウン(それはそれができる前にhandeledされる)、あなたはPreviewMouseUpを使用する必要はありませんので。
別のMVVMフレンドリーなソリューション(私は答えに満足していなかった)。
表示:
<Slider Maximum="100" Value="{Binding SomeValue}"/>
のViewModelます:
public class SomeViewModel : INotifyPropertyChanged
{
private readonly object _someValueLock = new object();
private int _someValue;
public int SomeValue
{
get { return _someValue; }
set
{
_someValue = value;
OnPropertyChanged();
lock (_someValueLock)
Monitor.PulseAll(_someValueLock);
Task.Run(() =>
{
lock (_someValueLock)
if (!Monitor.Wait(_someValueLock, 1000))
{
// do something here
}
});
}
}
}
これは、動作(与えられた例で1000
ミリ秒)遅れています。新しいタスクは、スライダによって行わすべての変更(マウスまたはキーボードのいずれかによって)のために作成されています。タスクを開始する前に、の信号の(Monitor.PulseAll
を使用することにより、おそらくMonitor.Pulse
は十分だろう)を停止し、すでにタスク(もしあれば)を実行します。 の何かの部分Monitor.Wait
がタイムアウト時間内に信号を取得していない場合にのみ発生します。
なぜ、この解決策?私は、産卵行動を好むか、ビューに取り扱い、不要なイベントを持っていません。どちらか(バインディングを使用する場合は特に、柔軟性のトンを追加します)各値の変更やユーザ操作の終了時に反応するために、すべてのコードが一つの場所にあり、必要に応じて余分なイベントは、ViewModel
は選択肢を持っていません。
ここでは、この問題に加えて、キーボードと同じものを扱う動作です。 https://gist.github.com/4326429する
これは、コマンドとValueプロパティを公開します。値は、コマンドのパラメータとして渡されます。あなたはvalueプロパティにデータバインド(とのviewmodelにそれを使用する)ことができます。あなたは、コードビハインドのアプローチのためのイベントハンドラを追加することもできます。
<Slider>
<i:Interaction.Behaviors>
<b:SliderValueChangedBehavior Command="{Binding ValueChangedCommand}"
Value="{Binding MyValue}" />
</i:Interaction.Behaviors>
</Slider>
私のソリューションは、基本的には、さらにいくつかのフラグとサントのソリューションです。私にとっては、スライダが
読み込みストリームまたはユーザー操作(マウスドラッグからまたは矢印キーなどを使用してのいずれか)のいずれかから更新されていますまず、私は流れを読んでからスライダーの値を更新するためのコードを書いていた。
delegate void UpdateSliderPositionDelegate();
void UpdateSliderPosition()
{
if (Thread.CurrentThread != Dispatcher.Thread)
{
UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
Dispatcher.Invoke(function, new object[] { });
}
else
{
double percentage = 0; //calculate percentage
percentage *= 100;
slider.Value = percentage; //this triggers the slider.ValueChanged event
}
}
私は、ユーザーがマウスのドラッグでスライダーを操作した際に撮影し、私のコードを追加します:
<Slider Name="slider"
Maximum="100" TickFrequency="10"
ValueChanged="slider_ValueChanged"
Thumb.DragStarted="slider_DragStarted"
Thumb.DragCompleted="slider_DragCompleted">
</Slider>
そして背後にあるコードを追加します:
/// <summary>
/// True when the user is dragging the slider with the mouse
/// </summary>
bool sliderThumbDragging = false;
private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
sliderThumbDragging = true;
}
private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
sliderThumbDragging = false;
}
ユーザーがマウスドラッグでスライダーの値を更新すると、値がまだ読んでUpdateSliderPosition()
を呼び出すされたストリームに起因して変化します。競合を防ぐために、UpdateSliderPosition()
を変更する必要がありました。
delegate void UpdateSliderPositionDelegate();
void UpdateSliderPosition()
{
if (Thread.CurrentThread != Dispatcher.Thread)
{
UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
Dispatcher.Invoke(function, new object[] { });
}
else
{
if (sliderThumbDragging == false) //ensure user isn't updating the slider
{
double percentage = 0; //calculate percentage
percentage *= 100;
slider.Value = percentage; //this triggers the slider.ValueChanged event
}
}
}
これは、競合を回避しますが、、我々はまだ値がユーザーまたはUpdateSliderPosition()
の呼び出しによって更新されているかどうかを判断することはできません。これは、さらに別のフラグでUpdateSliderPosition()
内から設定されたこの時間は固定されている。
/// <summary>
/// A value of true indicates that the slider value is being updated due to the stream being read (not by user manipulation).
/// </summary>
bool updatingSliderPosition = false;
delegate void UpdateSliderPositionDelegate();
void UpdateSliderPosition()
{
if (Thread.CurrentThread != Dispatcher.Thread)
{
UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
Dispatcher.Invoke(function, new object[] { });
}
else
{
if (sliderThumbDragging == false) //ensure user isn't updating the slider
{
updatingSliderPosition = true;
double percentage = 0; //calculate percentage
percentage *= 100;
slider.Value = percentage; //this triggers the slider.ValueChanged event
updatingSliderPosition = false;
}
}
}
最後に、我々はスライダがUpdateSliderPosition()
するユーザまたはコールによって更新されているかどうかを検出することができます:
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (updatingSliderPosition == false)
{
//user is manipulating the slider value (either by keyboard or mouse)
}
else
{
//slider value is being updated by a call to UpdateSliderPosition()
}
}
誰かを役に立てば幸い!
ユーザーが(つまり、どこかのトラックバーでクリック)の値を変更するには、親指を使用していない場合でも、あなたはポインタを押すとキャプチャのためにあなたのスライダーにイベントハンドラをアタッチできる情報を終了イベントを失いました。あなたは、キーボードイベントのために同じことを行うことができます。
var pointerPressedHandler = new PointerEventHandler(OnSliderPointerPressed);
slider.AddHandler(Control.PointerPressedEvent, pointerPressedHandler, true);
var pointerCaptureLostHandler = new PointerEventHandler(OnSliderCaptureLost);
slider.AddHandler(Control.PointerCaptureLostEvent, pointerCaptureLostHandler, true);
var keyDownEventHandler = new KeyEventHandler(OnSliderKeyDown);
slider.AddHandler(Control.KeyDownEvent, keyDownEventHandler, true);
var keyUpEventHandler = new KeyEventHandler(OnSliderKeyUp);
slider.AddHandler(Control.KeyUpEvent, keyUpEventHandler, true);
「魔法」は、ここで私たちはスライダー「内部」のイベントを取得することができます最後に真のパラメータとのAddHandlerです。 イベントハンドラます:
private void OnKeyDown(object sender, KeyRoutedEventArgs args)
{
m_bIsPressed = true;
}
private void OnKeyUp(object sender, KeyRoutedEventArgs args)
{
Debug.WriteLine("VALUE AFTER KEY CHANGE {0}", slider.Value);
m_bIsPressed = false;
}
private void OnSliderCaptureLost(object sender, PointerRoutedEventArgs e)
{
Debug.WriteLine("VALUE AFTER CHANGE {0}", slider.Value);
m_bIsPressed = false;
}
private void OnSliderPointerPressed(object sender, PointerRoutedEventArgs e)
{
m_bIsPressed = true;
}
ユーザーは、現在のスライダー(クリック、ドラッグやキーボード)を操作しているとき、m_bIsPressedメンバーはtrueになります。それは、かつて行わfalseにリセットされます。
private void OnValueChanged(object sender, object e)
{
if(!m_bIsPressed) { // do something }
}
私は@sinatrによって回答が気に入っています。
私の溶液上の回答に基づいて: このソリューションは、多くのコードをクリーンアップ機構をカプセル化します。
public class SingleExecuteAction
{
private readonly object _someValueLock = new object();
private readonly int TimeOut;
public SingleExecuteAction(int timeOut = 1000)
{
TimeOut = timeOut;
}
public void Execute(Action action)
{
lock (_someValueLock)
Monitor.PulseAll(_someValueLock);
Task.Run(() =>
{
lock (_someValueLock)
if (!Monitor.Wait(_someValueLock, TimeOut))
{
action();
}
});
}
}
としてあなたのクラスでそれを使用します:
public class YourClass
{
SingleExecuteAction Action = new SingleExecuteAction(1000);
private int _someProperty;
public int SomeProperty
{
get => _someProperty;
set
{
_someProperty = value;
Action.Execute(() => DoSomething());
}
}
public void DoSomething()
{
// Only gets executed once after delay of 1000
}
}
<Slider x:Name="PositionSlider" Minimum="0" Maximum="100"></Slider>
PositionSlider.LostMouseCapture += new MouseEventHandler(Position_LostMouseCapture);
PositionSlider.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(Position_DragCompleted));
スライダーのこのサブクラスのバージョンは動作します:
public class NonRealtimeSlider : Slider
{
static NonRealtimeSlider()
{
var defaultMetadata = ValueProperty.GetMetadata(typeof(TextBox));
ValueProperty.OverrideMetadata(typeof(NonRealtimeSlider), new FrameworkPropertyMetadata(
defaultMetadata.DefaultValue,
FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
defaultMetadata.PropertyChangedCallback,
defaultMetadata.CoerceValueCallback,
true,
UpdateSourceTrigger.Explicit));
}
protected override void OnThumbDragCompleted(DragCompletedEventArgs e)
{
base.OnThumbDragCompleted(e);
GetBindingExpression(ValueProperty)?.UpdateSource();
}
}