programing

WPF: 사용자가 드래그한 후 트리거하는 이벤트가 있는 슬라이더

goodsources 2023. 4. 26. 23:17
반응형

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

되고 1000ms) 연산을 수행합니다.슬라이더(마우스 또는 키보드)의 모든 변경 사항에 대해 새 작업이 만들어집니다.작업을 시작하기 전에 신호를 보냅니다(사용).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

반응형