wpf - ejemplo - arrastrar y soltar jquery
WPF: Control deslizante con un evento que se desencadena después de que un usuario arrastra (9)
Además de utilizar el evento Thumb.DragCompleted
, también puede usar ValueChanged
y Thumb.DragStarted
, de esta manera no pierde funcionalidad cuando el usuario modifica el valor presionando las teclas de flecha o haciendo clic en la barra deslizadora.
Xaml:
<Slider ValueChanged="Slider_ValueChanged"
Thumb.DragStarted="Slider_DragStarted"
Thumb.DragCompleted="Slider_DragCompleted"/>
Código detrás:
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);
}
Actualmente estoy haciendo un reproductor de MP3 en WPF, y quiero hacer un control deslizante que permita al usuario buscar una posición particular en un MP3 deslizando el control deslizante hacia la izquierda o hacia la derecha.
He intentado usar el evento ValueChanged
pero eso se dispara cada vez que se cambia su valor, así que si lo arrastras, el evento se disparará varias veces, quiero que el evento solo se active cuando el usuario haya terminado de arrastrar el control deslizante y luego obtener el nuevo valor
¿Cómo puedo conseguir esto?
[Actualizar]
Encontré esta publicación en MSDN, que básicamente trata sobre lo mismo, y llegaron a dos "soluciones"; ya sea ValueChanged
el ValueChanged
deslizante o invocando un DispatcherTimer
en el evento ValueChanged
que invoca la acción después de un intervalo de tiempo.
¿Puedes encontrar algo mejor que los dos mencionados anteriormente?
Aquí hay un comportamiento que maneja este problema más lo mismo con el teclado. https://gist.github.com/4326429
Expone las propiedades de Comando y Valor. El valor se pasa como el parámetro del comando. Puede enlazar datos a la propiedad value (y usarla en el viewmodel). Puede agregar un controlador de eventos para un enfoque de código subyacente.
<Slider>
<i:Interaction.Behaviors>
<b:SliderValueChangedBehavior Command="{Binding ValueChangedCommand}"
Value="{Binding MyValue}" />
</i:Interaction.Behaviors>
</Slider>
Esta versión subclase del control deslizante funciona como lo desee:
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();
}
}
Mi solución es básicamente la solución de Santo con algunas banderas más. Para mí, el control deslizante se está actualizando desde la lectura de la secuencia o la manipulación del usuario (ya sea desde la función de arrastrar del mouse o usando las teclas de flecha, etc.)
Primero, escribí el código para actualizar el valor del control deslizante al leer la transmisión:
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
}
}
Luego agregué mi código que capturó cuando el usuario estaba manipulando el control deslizante con un arrastre del mouse:
<Slider Name="slider"
Maximum="100" TickFrequency="10"
ValueChanged="slider_ValueChanged"
Thumb.DragStarted="slider_DragStarted"
Thumb.DragCompleted="slider_DragCompleted">
</Slider>
Y agregó el código detrás:
/// <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;
}
Cuando el usuario actualiza el valor del control deslizante con un arrastre del mouse, el valor seguirá cambiando debido a la secuencia que se está leyendo y llamando a UpdateSliderPosition()
. Para evitar conflictos, se tuvo que cambiar 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
}
}
}
Si bien esto evitará conflictos, aún no podemos determinar si el usuario está actualizando el valor o mediante una llamada a UpdateSliderPosition()
. Esto se soluciona con otra bandera, esta vez configurada desde 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;
}
}
}
Finalmente, podemos detectar si el control deslizante está siendo actualizado por el usuario o mediante la llamada a 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()
}
}
Espero que ayude a alguien!
Otra solución amigable MVVM (no estaba contento con las respuestas)
Ver:
<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
}
});
}
}
}
Es una operación retrasada (en 1000
ms en ejemplo dado). Se crea una nueva tarea para cada cambio realizado con el control deslizante (ya sea con el mouse o el teclado). Antes de comenzar la tarea, señala (utilizando Monitor.PulseAll
, quizás incluso Monitor.Pulse
sería suficiente) para ejecutar ya las tareas (si las hay) para detener. Hacer algo solo ocurrirá cuando Monitor.Wait
no reciba señal dentro del tiempo de espera.
¿Por qué esta solución? No me gusta el comportamiento de desove ni el manejo innecesario de eventos en la Vista. Todo el código está en un solo lugar, no se necesitan eventos adicionales, ViewModel
tiene la opción de reaccionar en cada cambio de valor o al final de la operación del usuario (lo que agrega mucha flexibilidad, especialmente cuando se utiliza el enlace).
Puedes usar el evento ''DragCompleted'' del pulgar para esto. Desafortunadamente, esto solo se activa al arrastrar, por lo que deberá controlar otros clics y presionar las teclas por separado. Si solo desea que se pueda arrastrar, puede desactivar estos medios para mover el control deslizante configurando LargeChange en 0 y Focusable en false.
Ejemplo:
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
Si desea obtener la información de finalización de manipulación incluso si el usuario no está utilizando el control para cambiar el valor (es decir, hacer clic en algún lugar de la barra de seguimiento), puede adjuntar un controlador de evento a su control deslizante para el puntero presionado y capturar eventos perdidos. Puedes hacer lo mismo para los eventos de teclado
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);
La "magia" aquí es el AddHandler con el parámetro verdadero al final que nos permite obtener los eventos deslizantes "internos". Los controladores de eventos:
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;
}
El miembro m_bIsPressed será verdadero cuando el usuario esté manipulando actualmente el control deslizante (haga clic, arrastre o el teclado). Se restablecerá a falso una vez hecho.
private void OnValueChanged(object sender, object e)
{
if(!m_bIsPressed) { // do something }
}
<Slider PreviewMouseUp="MySlider_DragCompleted" />
funciona para mi.
El valor que desea es el valor después de un evento de mousup, ya sea en los clics en el lateral o después de un arrastre del controlador.
Debido a que MouseUp no genera un túnel (se administra antes de que pueda), debe usar PreviewMouseUp.
<Slider x:Name="PositionSlider" Minimum="0" Maximum="100"></Slider>
PositionSlider.LostMouseCapture += new MouseEventHandler(Position_LostMouseCapture);
PositionSlider.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(Position_DragCompleted));