WPF: 사용자가 드래그한 후 트리거하는 이벤트가 있는 슬라이더
저는 현재 WPF에서 MP3 플레이어를 만들고 있는데, 사용자가 왼쪽이나 오른쪽으로 슬라이더를 움직여 MP3에서 특정 위치를 찾을 수 있는 슬라이더를 만들고 싶습니다.
는 사해보았다니습용▁the다▁using니를 사용해 보았습니다.ValueChanged
이벤트는 값이 변경될 때마다 트리거되므로, 이벤트를 끌면 이벤트가 여러 번 실행됩니다. 사용자가 슬라이더를 끌었을 때만 이벤트가 실행되고 새 값을 가져옵니다.
어떻게 하면 이를 달성할 수 있을까요?
[업데이트]
저는 MSDN에서 기본적으로 같은 것을 논의하는 이 게시물을 발견했고, 그들은 슬라이더를 하위 분류하거나 호출하는 두 가지 "해결책"을 생각해냈습니다.DispatcherTimer
에 시대에ValueChanged
시간 경과 후 작업을 호출하는 이벤트입니다.
위에 언급된 두 가지보다 더 나은 것을 생각해 낼 수 있습니까?
사하는것외를 하는 것 에도.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);
}
썸의 'DragCompleted' 이벤트를 사용할 수 있습니다.안타깝게도 이것은 끌 때만 실행되므로 다른 클릭과 키 누르기를 별도로 처리해야 합니다.끌 수 있도록 하려면 Large Change를 0으로 설정하고 Focusable을 false로 설정하여 슬라이더 이동 수단을 비활성화할 수 있습니다.
예:
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
<Slider PreviewMouseUp="MySlider_DragCompleted" />
저한테는 효과가 있어요.
원하는 값은 마우스업 이벤트 후 측면 클릭 시 또는 핸들 드래그 후의 값입니다.
MouseUp은 터널링되지 않으므로 미리 보기를 사용해야 합니다.마우스를 위로.
MVVM 친화적인 또 다른 솔루션(답변이 만족스럽지 않음)
보기:
<Slider Maximum="100" Value="{Binding SomeValue}"/>
모델 보기:
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
ms) 연산을 수행합니다.슬라이더(마우스 또는 키보드)의 모든 변경 사항에 대해 새 작업이 만들어집니다.작업을 시작하기 전에 신호를 보냅니다(사용).Monitor.PulseAll
심지어는Monitor.Pulse
이미 실행 중인 작업(있는 경우)을 중지할 수 있습니다.Do something 파트는 다음과 같은 경우에만 발생합니다.Monitor.Wait
시간 초과 내에 신호를 수신하지 않습니다.
이 솔루션을 선택해야 하는 이유보기에서 산란 동작이나 불필요한 이벤트 처리를 하는 것을 좋아하지 않습니다.가 한하지 않습니다.ViewModel
에서는 값이 변경될 때마다 또는 사용자 작업이 끝날 때마다 반응할 수 있습니다(특히 바인딩을 사용할 때).
제 구현은 @Alan과 @SandRock의 답변을 기반으로 합니다.
public class SliderValueChangeByDragBehavior : Behavior<Slider>
{
private bool hasDragStarted;
/// <summary>
/// On behavior attached.
/// </summary>
protected override void OnAttached()
{
AssociatedObject.AddHandler(Thumb.DragStartedEvent, (DragStartedEventHandler)Slider_DragStarted);
AssociatedObject.AddHandler(Thumb.DragCompletedEvent, (DragCompletedEventHandler)Slider_DragCompleted);
AssociatedObject.ValueChanged += Slider_ValueChanged;
base.OnAttached();
}
/// <summary>
/// On behavior detaching.
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(Thumb.DragStartedEvent, (DragStartedEventHandler)Slider_DragStarted);
AssociatedObject.RemoveHandler(Thumb.DragCompletedEvent, (DragCompletedEventHandler)Slider_DragCompleted);
AssociatedObject.ValueChanged -= Slider_ValueChanged;
}
private void updateValueBindingSource()
=> BindingOperations.GetBindingExpression(AssociatedObject, RangeBase.ValueProperty)?.UpdateSource();
private void Slider_DragStarted(object sender, DragStartedEventArgs e)
=> hasDragStarted = true;
private void Slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
hasDragStarted = false;
updateValueBindingSource();
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!hasDragStarted)
updateValueBindingSource();
}
}
다음과 같은 방식으로 적용할 수 있습니다.
...
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:myWhateverNamespace="clr-namespace:My.Whatever.Namespace;assembly=My.Whatever.Assembly"
...
<Slider
x:Name="srUserInterfaceScale"
VerticalAlignment="Center"
DockPanel.Dock="Bottom"
IsMoveToPointEnabled="True"
Maximum="{x:Static localLibraries:Library.MAX_USER_INTERFACE_SCALE}"
Minimum="{x:Static localLibraries:Library.MIN_USER_INTERFACE_SCALE}"
Value="{Binding Source={x:Static localProperties:Settings.Default}, Path=UserInterfaceScale, UpdateSourceTrigger=Explicit}">
<i:Interaction.Behaviors>
<myWhateverNamespace:SliderValueChangeByDragBehavior />
</i:Interaction.Behaviors>
</Slider>
UpdateSourceTrigger를 동작에 따라 명시적으로 설정했습니다.그리고 너겟 패키지 마이크로소프트가 필요합니다.Xaml.행동().Wpf/.Up.관리됨).
다음은 키보드를 사용하여 이 문제와 동일한 문제를 해결하는 동작입니다.https://gist.github.com/4326429
명령 및 값 속성을 표시합니다.이 값은 명령의 매개 변수로 전달됩니다.값 속성에 데이터를 바인딩하여 뷰 모델에 사용할 수 있습니다.코드백 접근 방식에 대한 이벤트 처리기를 추가할 수 있습니다.
<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 멤버가 참이 됩니다.완료되면 false로 재설정됩니다.
private void OnValueChanged(object sender, object e)
{
if(!m_bIsPressed) { // do something }
}
이 하위 분류된 Slider 버전은 원하는 대로 작동합니다.
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();
}
}
저는 @sinatr의 Answer를 좋아했습니다.
위의 답변을 기반으로 한 My Solution:이 솔루션은 코드를 많이 정리하고 메커니즘을 캡슐화합니다.
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
}
}
성공을 위한 솔루션UI3 v1.2.2는 다음과 같습니다.
Xaml 파일:
<Slider Margin="10, 0" MinWidth="200" LargeChange="0.5"
TickPlacement="BottomRight" TickFrequency="10"
SnapsTo="StepValues" StepFrequency="5"
Maximum="719"
Value="{x:Bind Path=XamlViewModel.XamlSliderToDateInt, Mode=TwoWay}">
</Slider>
To-Date 슬라이더 속성:
private int _sliderToDateInt;
public int XamlSliderToDateInt
{
get { return _sliderToDateInt; }
set
{
SetProperty(ref _sliderToDateInt, value);
_myDebounceTimer.Debounce(() =>
{
this.XamlSelectedTimeChangedTo = TimeSpan.FromMinutes(value);
// time-expensive methods:
this.XamlLCModel = _myOxyPlotModel.UpdatePlotModel(_myLCPowerRecList, XamlSliderFromDateInt, XamlSliderToDateInt, _myOxyPlotPageOptions);
this.XamlTRModel = _myOxyPlotModel.UpdatePlotModel(_myTRPowerRecList, XamlSliderFromDateInt, XamlSliderToDateInt, _myOxyPlotPageOptions);
},
TimeSpan.FromSeconds(0.6));
}
}
타이머 선언:
private DispatcherQueueTimer _myDebounceTimer;
생성자의 타이머 초기화:
_myDebounceTimer = _dispatcherQueue.CreateTimer();
방법 _myOxyPlotModel.슬라이더를 끌어 XamlSliderToDateInt 속성이 훨씬 빠르게 업데이트되는 경우에도 UpdatePlotModel()은 0.6초마다 호출됩니다.
마우스 버튼을 해제하거나 하지 않고 드래그를 중지한 후 타이머가 0.6초로 카운트되고 my oxyplot-methods가 호출되는 것처럼 느껴집니다.
Debounce() 메서드는 네임스페이스 Community에 속합니다.툴킷.WinUI.UI.
이것은 저에게 효과가 있고 모든 각도를 커버합니다.Peter의 답변에 따라 미리 보기를 사용할 수 있습니다.사용자가 드래그를 완료하면 마우스를 위로 이동하여 처리할 수 있습니다.그런 다음 화살표 키를 기준으로 값 변경을 처리하기 위해 미리 보기 키를 사용할 수 있습니다.
<Slider PreviewMouseUp="MySlider_DragCompleted" PreviewKeyUp="MySlider_KeyCompleted" />
상호 작용(현재 XAML 동작) 라이브러리에서 이 명령을 사용하는 경우:
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="PreviewKeyUp">
<behaviors:InvokeCommandAction Command="{Binding yourcommand}"/>
</behaviors:EventTrigger>
<behaviors:EventTrigger EventName="PreviewMouseUp">
<behaviors:InvokeCommandAction Command="{Binding yourcommand}"/>
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Slider x:Name="PositionSlider" Minimum="0" Maximum="100"></Slider>
PositionSlider.LostMouseCapture += new MouseEventHandler(Position_LostMouseCapture);
PositionSlider.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(Position_DragCompleted));
언급URL : https://stackoverflow.com/questions/723502/wpf-slider-with-an-event-that-triggers-after-a-user-drags
'programing' 카테고리의 다른 글
셀(1,1) = 500 * 100은 오버플로를 유발하지만 50000 * 100은 오버플로를 유발하지 않는 이유는 무엇입니까? (0) | 2023.04.26 |
---|---|
재귀 파일 목록의 깊이를 제한하는 방법은 무엇입니까? (0) | 2023.04.26 |
ng server는 파일 변경을 자동으로 감지하지 않습니다. (0) | 2023.04.26 |
.NET / C# - char[]를 문자열로 변환 (0) | 2023.04.26 |
셀에서 쉼표를 줄 바꿈으로 바꿉니다. (0) | 2023.04.21 |