Warm tip: This article is reproduced from serverfault.com, please click

UWP and ReactiveUI – How do I (sometimes) synchronise two sliders values?

发布于 2020-11-27 15:04:47

In my XAML I have two sliders and a toggle button:

<Slider
    x:Name="rightSlider"
    Header="Right"
    Maximum="20"
    Minimum="0" />
<Slider
    x:Name="suffixSlider"
    Header="Suffix"
    Maximum="20"
    Minimum="0" />
<ToggleButton
    x:Name="toggleSuffixLockButton"
    Content="Lock" />

In my view code behind I have bound them as follows:

this.Bind(ViewModel,
    viewModel => viewModel.RightSliderValue,
    view => view.rightSlider.Value)
    .DisposeWith(disposableRegistration);
this.Bind(ViewModel,
    viewModel => viewModel.SuffixSliderValue,
    view => view.suffixSlider.Value)
    DisposeWith(disposableRegistration);
this.OneWayBind(ViewModel,
    viewModel => viewModel.SuffixIsLocked,
    view => view.suffixSlider.IsEnabled,
    value => value == false ? true : false)
    .DisposeWith(disposableRegistration);
this.Bind(ViewModel,
    viewModel => viewModel.SuffixIsLocked,
    view => view.toggleSuffixLockButton.IsChecked)
    .DisposeWith(disposableRegistration);

My view model has these declarations:

[Reactive]
public double RightSliderValue { get; set; }
[Reactive]
public double SuffixSliderValue { get; set; }
[Reactive]
public bool SuffixIsLocked { get; set; }

When toggleSuffixLockButton is NOT checked, the user should be able to alter the two sliders independently; this works.

When toggleSuffixLockButton is NOT checked, I want the suffixSlider element to be enabled; this works.

When toggleSuffixLockButton IS checked, I want the suffixSlider element to be disabled; this works.

However, as soon as the toggleSuffixLockButton is checked I also want the suffixSlider value to be immediately set to the value of rightSlider and then every time rightSlider changes (while toggleSuffixLockButton is checked) then suffixSlider should also change to the same value, until toggleSuffixLockButton is NOT checked; this I cannot figure out how to do properly.

I thought had a vague idea of how to do it but I started going round in circles.

Can anyone help?

Extra code (in the view model) added to show the additional problem of ‘double processing’:

private readonly ObservableAsPropertyHelper<string> _theResult;
public string TheResult => _theResult.Value;

_theResult = this
   .WhenAnyValue(
   viewModel => viewModel.RightSliderValue,
   viewModel => viewModel.SuffixSliderValue,
   (right, suffix) => new GenerationParameters(right, suffix))
// .Throttle(TimeSpan.FromMilliseconds(200))
   .DistinctUntilChanged()
   .SelectMany(GetResult) // GetResult simply returns a string based on the right and suffix values.
   .ObserveOn(RxApp.MainThreadScheduler)
   .ToProperty(this, x => x.TheResult, String.Empty);

Whenever locking is on (as per the code supplied below), _theResult gets recalculated for both the RightSliderValue change and the SuffixSliderValue change, one straight after the other (or maybe at the same time since it’s async, I’m not sure).

When the .Throttle() code is uncommented (as I will have it in my actual application) there’s no problem but I was wondering if there was a way not to have to throttle it (just in case I need to do this in future projects).

Questioner
GarryP
Viewed
0
Đøharrrck 2020-12-02 23:40:21

I would add the logic for that into the view model so the view only contains the binding. It should work using these two WhenAnyValue statements in the view models constructor, resp. WhenActivated block:

        // sets the suffix slider to the position of right slider
        // if suffix gets locked
        this.WhenAnyValue(x => x.SuffixIsLocked)
            .Where(locked => locked)
            .Subscribe(_ => SuffixSliderValue = RightSliderValue);

        // keeps both sliders in sync if suffix is locked
        this.WhenAnyValue(x => x.RightSliderValue)
            .Where(_ => SuffixIsLocked)
            .Subscribe(_ => SuffixSliderValue = RightSliderValue);

        // second part: GetResult

        // observable for right slider if not SuffixIsLocked
        IObservable<GenerationParameters> parametersFromRight = this
            .WhenAnyValue(
            viewModel => viewModel.RightSliderValue,
            (right) => new GenerationParameters(right, SuffixSliderValue))
            .Where(_ => !SuffixIsLocked);

        // observable for suffix slider
        IObservable<GenerationParameters> parametersFromSuffix = this
            .WhenAnyValue(
            viewModel => viewModel.SuffixSliderValue,
            (suffix) => new GenerationParameters(RightSliderValue, suffix));

        // merge and create property
        _theResult = parametersFromRight
            .Merge(parametersFromSuffix)
            .DistinctUntilChanged()
            .SelectMany(GetResult)
            .ObserveOn(RxApp.MainThreadScheduler)
            .ToProperty(this, x => x.TheResult, string.Empty);

        // catch exceptions
        _theResult.ThrownExceptions.Subscribe(ex => Console.WriteLine(ex.Message));

Edit: Updated the code for the second part of the question. Note: This will only catch exceptions after the merge, i.e. exceptions in GetResult but not those occurring in the constructor of GenerationParameters.