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).
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
.
That works really nicely in my application as I have it at the moment, many thanks. One concern I have now is that while the locking is on it causes a separate observable to be recalculated twice. I’ve added the extra code to my post above. Is there anything I can do to avoid this? Apologies for not adding this code at first but I’m still trying to learn ReactiveUI in small steps while trying not to learn bad ways of doing things.
DistinctUntilChanged
does not work as it is because you create a new instance with each change of any of the slider values. You can either change the equality behavior of theGenerationParameters
in the class/struct itself or write anIEqualityComparer
which you use as parameter inDistinctUntilChanged
with anEquals
method that provides true if the values for right and suffix are the same for two instances.Thanks for getting back to me, again. I created a new IEqualityComparer class but that’s not working as the SuffixSliderValue is changing first then the RightSliderValue is changing. So when they are locked at 12 and I change RightSliderValue from 12 to 13, I get the sequence (Right 12, Suffix 13), (Right 13, Suffix 13), which are both different, and therefore unequal. Or maybe I’m just using it wrong. I’ll need to have a bit of a think about this but any further help you can give would be very much appreciated as I’m working completely alone with no-one to ask questions of (except here).
Right, didn't think of that. For me it is working now if I split the
_theResult = this.WhenAnyValue
in two statements and add a 'Where(_ => !SuffixIsLocked)' to the first one so that changes from the right slider are ignored when the toggle button is locked. Btw: is it correct that you have aSelectMany(GetResult)
statement instead ofSelect
? This should not even compile because this will return an observable for chars and not for a string.The SelectMany() seem to be working fine. If I try to use Select() instead of SelectMany() I get a CS0411 error, which can probably be remedied by doing the right thing but I don’t know what the right thing is at the moment. I’ll look at splitting the _theResult = this.WhenAnyValue statement into two but that sounds confusing as it sounds like I would be giving the same variable two different ‘values’ at the same time (or one cancelling the other out), which seems strange but I’m probably not understanding what you mean. I need to experiment and see what happens.