Detection of circular references in RxUI - reactiveui

I set up 2 checkboxes that negate eachother.
The behaviour that I thought to see was an infintite loop (checkbox 1 checks 2 check 1 checks 2...)
Instead the propagation of change is stopped after the other other checkbox is checked by RxUI.
Is there some kind of detected of circular references build into RxUI?
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
namespace rxnested
{
public class VM01 : ReactiveObject
{
[Reactive]
public bool Prop1 { get; set; }
[Reactive]
public bool Prop2 { get; set; }
public VM01()
{
this.WhenAnyValue(x => x.Prop1)
.Subscribe(x => Prop2 = !x);
this.WhenAnyValue(x => x.Prop2)
.Subscribe(x => Prop1 = !x);
}
}
}
<Window x:Class="rxnested.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:rxnested"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<Window.DataContext>
<local:VM01></local:VM01>
</Window.DataContext>
<StackPanel>
<CheckBox IsChecked="{Binding Prop1}"></CheckBox>
<CheckBox IsChecked="{Binding Prop2}"></CheckBox>
</StackPanel>
</Window>

I think that your expectation that your code should result in infinite loop is wrong.
There are 2 scenarios. Let's consider them separately.
1) Let's say that initially both properties have the same value (let's say it is false). Changing P1 to true will trigger first subscription, which will try setting up P2 into the reverse of the current value of P1 (reverse of true == false). That means that it will try to set false to something that was already false, and thus nothing will happen (that's what [Reactive] give you - before setting the property, it checks if it has changed).
2) Now P1 initially is true, and P2 is false. Changing P1 to false triggers first subscription, which is setting P2 into (!P1), which is in this case true. So, P2 changes from false to true. That triggers second subscription, which tries setting P1 into (!P2), which is in now false. But P1 is already false, so the property does not change (and thus the NotifyPropertyChanged does not fire).
So, the cycle is broken by not setting property blindly, but first checking if it changed at all (to avoid unnecessary NotifyPropertyChanged events).
This is even more clear when you realize that [Reactive] translates into:
private bool _prop2;
public bool Prop2
{
get { return _prop2; }
set { this.RaiseAndSetIfChanged(ref _prop2, value); }
}
Note the name - RaiseAndSetIfChanged.
Now, if you want to see an infinite loop.. simply change your code into this:
public VM01()
{
this.WhenAnyValue(x => x.Prop1)
.Subscribe(x => Prop2 = !Prop2);
this.WhenAnyValue(x => x.Prop2)
.Subscribe(x => Prop1 = !Prop1);
}
This gives me StackOverflowException.

Related

Sorting of ObservableCollection does not change TreeView

I have an UWP project c# where I declare an ObservableCollection of items, on the page there is a TreeView bound to the collection. When I load it everything is fine and the TreeView is populated.
I've then added a button to sort ascending and descending BUT when I sort it the TreeView is not changed; I've read a lot of similar questions but was not able to find a solution.
In one of them the solution was:
public ObservableCollection<MTreeViewPaz> Patients { get; private set; } = new ObservableCollection<MTreeViewPaz>();
... in the AppBar code behind on the AppBarToggleButton_Checked:
Patients = new ObservableCollection<MTreeViewPaz>(
from i in Patients orderby i.Data descending, i.Cognome, i.Nome select i);
The View is bind to Patients:
<winui:TreeView
x:Name="treeView"
Grid.Row="1"
Expanding="treeView_Expanding"
ItemInvoked="OnItemInvoked"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{x:Bind Patients}"
SelectionMode="Single" />
But nothing happens.
I've checked with the debugger and the Patients items are different before and after the sorting. So that part works fine, only is not shown.
Even it should be useless as ObservableCollection should rise events I've even tried to do this:
private ObservableCollection<MTreeViewPaz> _Patients;
public ObservableCollection<MTreeViewPaz> Patients
{
get { return _Patients; }
private set { Set(ref _Patients, value); }
}
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
But it does not work
Sorting of ObservableCollection does not change TreeView
For checking above code, I found you used default x:Bind mode(one time). And it will not response the Patients reset. please edit above to OneWay Mode.
ItemsSource="{x:Bind DataSource, Mode=OneWay}"

How to send more than one param with DelegateCommnad

I have Button in ListView Cell. On button click I need to perform two actions from ViewModel
Get the current record (to make modification in data)
Get the current Cell/View (to modify text, bg color of button)
However, I am able to perform single action at a time using DelegateCommand by passing Student and object param respectively. See my code below
public StudentAttendanceListPageViewModel()
{
//BtnTextCommand=new DelegateCommand<object>(SetBtnText);
ItemCommand=new DelegateCommand<Student>(BtnClicked);
}
public DelegateCommand<object> BtnTextCommand { get; private set; }
public void SetBtnText(object sender)
{
if (view.Text == "P")
{
view.Text = "A";
view.BackgroundColor= (Color)Application.Current.Resources["lighRedAbsent"];
}
}
public DelegateCommand<Student> ItemCommand { get; }
public void BtnClicked(Student objStudent)
{
objStudent.AbsentReasonId="1001";
objStudent.AttendanceTypeStatusCD = "Absent";
objStudent.AttendanceTypeStatusId = "78001"
}
This is Button code
<Button x:Name="mybtn"
Command="{Binding Source={x:Reference ThePage}, Path=BindingContext.ItemCommand}"
CommandParameter="{Binding .}"
BackgroundColor="{DynamicResource CaribGreenPresent}"
Text="{Binding AttendanceTypeStatusId, Converter={x:StaticResource IDToStringConverter}}">
</Button>
If you see above code I have two methods SetBtnText and BtnClicked. How can I merge these two methods into one by passing Student and object params at a time in DelegateCommand?
You should bind the view's properties to the view model. Then pass the view model as command parameter and change whatever you want to change in the command and data binding will automatically update the view.
Example:
<Button Command="{Binding SomeCommand}"
Text="{Binding Text}">
</Button>
public class StudentViewModel
{
public StudentViewModel( Student student )
{
_text = $"Kick {student.Name}";
SomeCommand = new DelegateCommand( () => {
Text = "I have been kicked"
student.Exmatriculate();
SomeCommand.RaiseCanExecuteChanged();
},
() => student.IsMatriculated
);
}
public DelegateCommand SomeCommand { get; }
public string Text
{
get => _text;
set => SetProperty( ref _text, value );
}
private string _text;
}
As stated in the comments already, it's never ever necessary to pass the view to the view model. To me, it looks as if you don't have a view model in the first place, though, as your code only mentions Student (which most likely is part of the model), while there's no occurence of a StudentViewModel. You know, you do not bind to the model directly unless it's a trivial toy project.

Why doesn't my Xamarin.Forms Entry get empty in the UI when I set the bound string to ""?

I use the following method to set my variables:
private string _password = "";
public string Password
{
get => _password;
set {
_password = value;
this.OnPropertyChanged("Password");
this.OnPropertyChanged("_password");
}
}
This is within my ViewModel that I bind via:
<ContentPage.BindingContext>
<vm:MainPageViewModel/>
</ContentPage.BindingContext>
I have a Entry in my xaml with:
<Entry Text="{Binding Password,Mode=TwoWay}"
HorizontalOptions="Start"
VerticalOptions="Start">
</Entry>
I have no problem accessing the information in the entry within my ViewModel however when I call
Password = "";
The text of the Entry that gets shown within the UI on the screen stays the same. How do I erase the text within the Entry from my ViewModel?
The behavior happens in Android/iOS and UWP.
Do the following changes and it should work for you :
remove this this.OnPropertyChanged("_password"); as you notify on property changed and not on the field changed.
Hence it would look like:
public string Password
{
get => _password;
set {
_password = value;
this.OnPropertyChanged("Password");
}
}
Secondly, In my knowledge, you might want to do the Password change on MainThread something like:
Device.BeginInvokeOnMainThread(() => { Password = string.Empty; });
The reason being INotifyPropertyChanged is not thread safe! and you could be doing it in an async method

How can I make ASP.NET MVC 3 render Html.CheckBoxFor and the corresponding hidden element with reversed values?

I'm using ASP.NET MVC3 with Razor.
I have a boolean value in my model which I would like to render as a check box in my view. However, I would like the check box to indicate the reverse of the boolean state. In other words, selecting the check box should set the bound model object to false, not true, when the form is submitted. And vice versa.
I can do this to set the value attribute on the rendered check box input element:
#Html.CheckBoxFor(model => model.MyBoolean, new { value = "false" })
But the hidden input element that is automatically created still has a value of false. Thus they both have a value of false, which means the bound model object is always set to false.
So how can I force the HTML helper to set the hidden element to true and the check box element to false?
I know that (a) I could alter the model and the database, (b) I could alter the values with javascript just prior to submission, and (c) I could swap whatever value is received in the controller after submission. I may do one of these, but I'm not asking for possible solutions; I'm asking whether it is possible to make the HTML helper do as I wish. I have searched extensively and haven't seen this addressed anywhere in official or unofficial sources. It seems like they should have a "swap" option or something.
class ViewModel {
public bool MyBoolean {get;set;}
public bool DisplayValue {
get {
return ! MyBoolean ;
}
set {
MyBoolean = !value;
}
}
}
And bind to the DisplayValue as it's setter updates you MyBoolean property anyway.
EDIT:
After reading your question again:.
You could use HtmlHelper to do that - but instead of using a CheckBox you could use a dropdown. The dropdown will define the "oppisite" values and text.
myModelInstance.PossibleValues = new[] { new SelectListItem { Value = "false", Text = "Not false" }, new SelectListItem { Value = "true", Text = "Not true" } };
Notice how the description is the opposite meaning of what you want the model to be. So for eg. for true you may have text as "Hidden" and false you may have the text as "Visible", true for "Disabled" and false for "Enabled" etc.
Then in your View:
#Html.DropDownList(Model.MyBoolean.ToString(), Model.PossibleValues)
The model will be updated with the correct value without have to do boolean toggles before viewing or updating.
For future readers, in my own opinion, HtmlHelpers are designed to render Html (as the name suggests). My preference for creating different way to render items is to create EditFor and DisplayFor templates. To make sure this is highly reusable, I also create model designed specifically for these templates. With your design, my models and templates might look like:
/Models/Controller/ControllerActionViewModel.cs
public class ControllerActionViewModel
{
public ControllerActionViewModel()
{
this.CheckboxBoolTemplate = new CheckboxBoolTemplate(false, true);
}
[Display(Name = "My Boolean")]
public SelectBoolTemplate MyBoolean { get; set; }
}
/TemplateModels/ControllerActionViewModel.cs
public sealed class SelectBoolTemplate
{
private bool _valuesSwapped = false;
private bool? _value;
private bool _defaultValue = false;
public SelectBoolTemplate()
{
}
public SelectBoolTemplate(bool valuesSwapped)
{
this._valuesSwapped = valuesSwapped)
}
public SelectBoolTemplate(bool defaultValue, bool valuesSwapped)
{
this._defaultValue = defaultValue;
this._valuesSwapped = valuesSwapped)
}
public bool Value
{
get
{
if (this._value.HasValue)
{
return this._value.Value
}
return this._defaultValue;
}
set
{
this._value = value;
}
}
}
/Views/Shared/EditorTemplates/SelectBoolTemplate.cshtml
#model SelectBoolTemplate
#{
string propertyName = ViewContext.ViewData.ModelMetadata.PropertyName;
string fullPropertyName = ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
string labelText = ViewContext.ViewData.ModelMetadata.DisplayName
?? ViewContext.ViewData.ModelMetadata.PropertyName;
}
#Html.LabelForModel()
#Html.Checkbox(fullPropertyName, Model.Value)
I know this is too late for the original query but for anyone reading in future there's an alternative way to handle checkboxes with reversed false/true for checked/unchecked which requires no changes to the model - create a checkbox for false and a hidden field for true
<input id="#Html.IdFor(model => model.BoolProperty)" name="#Html.NameFor(model => model.BoolProperty)" type="checkbox" value="false" #(Model.BoolProperty? "" : "checked=\"checked\"" ) />
<input name="#Html.NameFor(model => model.BoolProperty)" type="hidden" value="true" />

LINQ filtering from viewModel and List<>

I need help with LINQ syntax or methodology, not sure which.
Here's my issue: I have a list of items (Bill, Bob, Ed) and I need to select and filter out anything that the user selected. So if the viewModel contains "Bob", then the LINQ statement should return "Bill","Ed." The trick is that the user can select multiple things, so the viewModel could contain "Ed", "Bob" and so the LINQ statement should return just "Bill."
The viewModel is an IEnumerable and the list of items is a List<>. I have something simple like this as a starting point:
c.Items.select(p=>p.Name)
where c.Items refers to the Bill, Bob and Ed above. Now I just need to filter out the viewModel selections and I'm struggling with the LINQ syntax. I've tried variations on != viewModel.selectedNames, which led nowhere, some variations using .contains and one using all.
var filteredItems = viewModel.selectedNames;
c.Items.Where(p => filteredItems.All(t => !p.Name.Contains(t)));
I'm currently feeling beached.
Perhaps something like:
var filteredNames = new HashSet<string>(viewModel.SelectedNames);
// nb: this is not strictly the same as your example code,
// but perhaps what you intended
c.Items.Where(p => !filteredNames.Contains(p.Name));
On a second look, perhaps you should restructure your view model slightly:
public class PeopleViewModel : ViewModelBaseOfYourLiking
{
public ObservableCollection<Person> AllPeople
{
get;
private set;
}
public ObservableCollection<Person> SelectedPeople
{
get;
private set;
}
public IEnumerable<Person> ValidPeople
{
get { return this.AllPeople.Except(this.SelectedPeople); }
}
// ...
At this point you'd do your wiring up in the view:
<ListBox ItemSource="{Binding AllPeople}"
SelectedItems="{Binding SelectedPeople}" />
<ItemsControl ItemsSource="{Binding ValidPeople}" />
In your view model's constructor you'd apply the appropriate eventing to ensure that ValidPeople got updated when needed:
public PeopleViewModel(IEnumerable<Person> people)
{
this.AllPeople = new ObservableCollection<Person>(people);
this.SelectedPeople = new ObservableCollection<Person>();
// wire up events to track ValidPeople (optionally do the same to AllPeople)
this.SelectedPeople.CollectionChanged
+= (sender,e) => { this.RaisePropertyChanged("ValidPeople"); };
}

Resources