This is the property that I defined inside a CustomButton:
public bool IsOn
{
get { return (bool)GetValue(IsOnProperty); }
set
{
SetValue(IsOnProperty, value);
if (IsOn)
VisualStateManager.GoToState(this, "On", true);
else
VisualStateManager.GoToState(this, "Off", true);
}
}
public static readonly DependencyProperty IsOnProperty =
DependencyProperty.Register("IsOn",
typeof(bool),
typeof(ImageButton),
new PropertyMetadata(false));
In my Xaml it is bound to another Boolean list below:
IsOn="{Binding Sender.IsPinned, Mode=OneWay}"
and Sender.IsPinned raises the PropertyChange
public bool IsPinned
{
get { return _model.IsPinned; }
set
{
_model.IsPinned = value;
RaisePropertyChanged("IsPinned");
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
When IsPinned is changed, IsOn doesn't see the change at all. But when I save the data and refresh the UI, the change is reflected on IsOn.
It seems like the PropertyChangedEventHandler is not implemented but when debugged, it calls the event.
I believe your code (not shown) is changing the _model.IsPinned directly and not changing the IsPinned property which has the binding notification. That would lead to an update failure to IsOn.
The backing variable _model.IsPinned which is used by IsPinned cannot be changed by anything other than the IsPinned setter; such that its change can be noticed by IsOn.
One has to change IsPinned property directly (which subsequently changes _model.IsPinned) otherwise the binding cannot pick up on any changes to the backing store _model.IsPinned.
-- Or ---
When the variable _model.IsPinned is changed outside of the setter, simply call RaisePropertyChanged("IsPinned"); to signify the change to IsOn.
That is how I solved it
public bool IsOn
{
get
{
return (bool)GetValue(IsOnProperty);
}
set
{
SetValue(IsOnProperty, value);
}
}
public static readonly DependencyProperty IsOnProperty =
DependencyProperty.Register("IsOn", typeof(bool), typeof(ImageButton), new PropertyMetadata(false, Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ImageButton img = (ImageButton)d;
if (img.IsOn)
VisualStateManager.GoToState(img, "On", true);
else
VisualStateManager.GoToState(img, "Off", true);
}
Related
I have been trying to detect it when these variables change, but I don't know how to do that since bools aren't supported by the "PropertyChanged" function.
I also tried using the communityToolKit, but I have no idea how to use that.
I want it to call the function "IconUpdater"
public class Status : INotifyPropertyChanged
{
public static bool isWorking { get; set; } = Preferences.Get("IsWorking", true);
public static bool isPaused { get; set; } = Preferences.Get("IsPaused", false);
public static void IconUpdater()
{
// The function I want to call \\
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can use PropertyChanged event to notify the changes of IsEnabled property in your viewmodel.
Here's the code snippet below for your reference:
public class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isWorking;
public bool IsEnabled
{
get
{
return _isWorking;
}
set
{
if(_isWorking != value)
{
_isWorking = value;
var args = new PropertyChangedEventArgs(nameof(IsEnabled));
PropertyChanged?.Invoke(this, args);
}
}
}
}
I recommend using the Community Toolkit MVVM package: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/
You can then simply do the following to use the INotifyPropertyChanged interface:
using CommunityToolkit.Mvvm;
public class MyViewModel : ObservableObject
{
private bool _myBool;
public bool MyBool
{
get => _myBool;
set => SetProperty(ref _myBool, value);
}
}
You can also modify the code in such a way that you directly call any other method from within the setter:
private bool _myBool;
public bool MyBool
{
get => _myBool;
set
{
SetProperty(ref _myBool, value);
IconUpdater();
}
}
Please mind that your class is using static properties. You cannot use INotifyPropertyChanged for that.
In my project I am setting a bool property as true when constructing my ViewModel.
In the View, I have a RadioButton which is bound to the value of this property. The first time I open the View, the RadioButton is "checked" (perfect, exactly what I want!).
However, if I close and then re-open the View, the RadioButton is not "checked", despite the bound property having a value of 'true'. In my constructor I am setting 'StaggeredMode' to true, but the setter is being called three times (firstly, value = true; secondly, value = false; thirdly, value = true)
Any help will be appreciated!
XAML:
<RadioButton GroupName="AppointmentStart"
Content="Staggered"
IsChecked="{Binding StaggeredMode, Mode=TwoWay}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="310,247,0,0" />
C#:
private bool staggeredMode;
public bool StaggeredMode
{
get { return staggeredMode; }
set { staggeredMode = value; }
}
Your StaggeredMode property should be observable so the view can be notified about property changes from your ViewModel.
You can implement INotifyPropertyChanged by yourself or use one of existing implementations like this, this or this.
Here is a basic implementation of INotifyPropertyChanged interface:
public abstract class ObservableObject : INotifyPropertyChanged
{
public virtual void RaisePropertyChanged(string propertyName)
{
OnPropertyChanged(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Then you derive your ViewModel from ObservableObject and raise OnPropertyChanged event every time your property value has been changed:
public class MyViewModel : ObservableObject
{
...
private bool staggeredMode;
public bool StaggeredMode
{
get { return staggeredMode; }
set
{
staggeredMode = value;
OnPropertyChanged("StaggeredMode");
}
}
...
}
In Xamarin.Forms I implemented a custom Picker.
The ItemsSource is set correctly. However when i change the selected item it does not update the property on my ViewModel.
The BindablePicker:
public class BindablePicker : Picker
{
public BindablePicker()
{
this.SelectedIndexChanged += OnSelectedIndexChanged;
}
public static BindableProperty ItemsSourceProperty =
BindableProperty.Create<BindablePicker, IEnumerable>(o => o.ItemsSource, default(IEnumerable), propertyChanged: OnItemsSourceChanged);
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), propertyChanged: OnSelectedItemChanged);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject bindable, IEnumerable oldvalue, IEnumerable newvalue)
{
var picker = bindable as BindablePicker;
picker.Items.Clear();
if (newvalue != null)
{
//now it works like "subscribe once" but you can improve
foreach (var item in newvalue)
{
picker.Items.Add(item.ToString());
}
}
}
private void OnSelectedIndexChanged(object sender, EventArgs eventArgs)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = Items[SelectedIndex];
}
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var picker = bindable as BindablePicker;
if (newvalue != null)
{
picker.SelectedIndex = picker.Items.IndexOf(newvalue.ToString());
}
}
}
The Xamlpage:
<controls:BindablePicker Title="Category"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
Grid.Row="2"/>
The ViewModel properties, didn't implement the NotifyPropertyChanged on the properties since they only need to be updated from the ´Viewto theViewModel`:
public Category SelectedCategory { get; set; }
public ObservableCollection<Category> Categories { get; set; }
When creating your BindableProperty:
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), propertyChanged: OnSelectedItemChanged);
without specifying the defaultBindingMode, the BindingMode is set to OneWay, meaning the Binding is updated from source (your view model) to target (your view).
This can be fixed by changing the defaultBindingMode:
public static BindableProperty SelectedItemProperty =
BindableProperty.Create<BindablePicker, object>(o => o.SelectedItem, default(object), BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
or, if it's the default you want for your picker, but want to update the source only in this view, you can specify the BindingMode for this instance of the Binding only:
<controls:BindablePicker Title="Category"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
Grid.Row="2"/>
Beside adding the Mode=TwoWay to my binding a had to change some things in my picker so it could work with the actual objects i had it bound to.
The Items property of the Xamarin Picker is an IList<string>
since all my items are added to it as a string it keeps the same indexed value.
Therefor the ItemsSource is changed to an IList:
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
I also modified the SelectedIndexChangedmethod so it doesn't retrieve the item from the Items but from the ItemsSource, wich is in my case an IList<Category>:
private void OnSelectedIndexChanged(object sender, EventArgs eventArgs)
{
if (SelectedIndex < 0 || SelectedIndex > Items.Count - 1)
{
SelectedItem = null;
}
else
{
SelectedItem = ItemsSource[SelectedIndex];
}
}
In my ViewModel i no longer use the ObservableCollection for my Categories but add these items to an IList<Category>.
The ObservableCollectionhas no use since when my BindablePicker binds to the ItemsSource the items are added to the internal IList<string>. when adding an item to the collection it will not be updated. I now update the entire ItemSourceif an item is changed.
I am binding a Datagrid to Observablecollection.Below is my ObservableCollection class.
But the Property changed is always NULL and it is null even after Making my XAML like this.
Please Guide me in this
Thanks!
<DataGridTextColumn Binding="{Binding, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged} Header = "Serial" />
public class itemobject
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged( String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int sno1;
public int Sno
{
get
{ return sno1; }
set
{
if (value != sno1)
{
sno1= value;
NotifyPropertyChanged("Sno");
}
}
}
In XAML you should specify which property exactly changed.
<DataGridTextColumn Binding="{Binding Path = Sno} Header = "Serial" />
And like i did, you need to create ViewModelBase class.
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable {
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null) {
var handle = PropertyChanged;
handle?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void Dispose() => PropertyChanged = null;
}
public class itemobject : ViewModelBase{
int sno1;
public int Sno{
get => sno1;
set{
if (value != sno1){
sno1= value;
OnPropertyChanged(nameof(Sno));
}
}
}
I've been trying my hand at creating a simple user control with dependencyproperty and binding it, but it doesn't seem to work, not sure why. I'll just dive straight into code, please ignore the fact that the control doesn't make sense, it's just for illustrative purposes (written in WP8 if that matters).
My simple user control, it's basically a line with a property to turn it off or on.
<Grid x:Name="LayoutRoot" Background="Transparent">
<Line Height="105" Width="105" X2="100" Y2="100" Visibility="{Binding LineVisible}" Stroke="#FFFC1515" StrokeThickness="5"/>
</Grid>
public partial class SimpleUserControl : UserControl
{
public SimpleUserControl()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty LineVisibleProperty = DependencyProperty.Register("LineVisible", typeof(bool), typeof(SimpleUserControl), new PropertyMetadata(new PropertyChangedCallback(OnLineVisibleChanged)));
public bool LineVisible
{
get { return (bool)GetValue(LineVisibleProperty); }
set { SetValue(LineVisibleProperty, value); }
}
private static void OnLineVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool newvalue = (bool)e.NewValue;
Visibility vis = newvalue ? Visibility.Visible : Visibility.Collapsed;
(d as SimpleUserControl).Visibility = vis;
}
}
The test app
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<uc:SimpleUserControl LineVisible="{Binding class1.Vis}"/>
</Grid>
public partial class MainPage : PhoneApplicationPage
{
public Class1 class1 { get; set; }
public MainPage()
{
InitializeComponent();
DataContext = this;
}
private void PhoneApplicationPage_Loaded_1(object sender, RoutedEventArgs e)
{
class1 = new Class1() { Vis = false };
}
}
The class1 that it's bound to
public class Class1 : INotifyPropertyChanged
{
private bool _vis;
public bool Vis
{
get { return _vis; }
set
{
_vis = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Vis"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
It doesn't seem to work, however, if it is set explicitly like below it works.
<uc:SimpleUserControl LineVisible="False"/>
I'm sure it's something simple, but I'm not seeing it.
Thanks for any help.
The problem was I was setting the DataContext = this in the UserControl and when binding to Vis in the testapp, it would override and search for Vis in the UserControl (which of course does not exist there). I did see binding errors in the debug output window which confirms this. The solution was to set the LayoutRoot of the UserControl to this as was mentioned in the link I posted earlier.
The Visibility property of WPF controls does not use bool values, it requires the Visibility enum. Thus you have two options:
Change LineVisibiltyProperty to Visibility instead of bool.
Use a converter to bind to bool and convert to Visibility.
I would suggest using the second option as this in my opinion is the better solution.
This might be helpful.