I'm trying to dynamically update an element's visibility by binding IsVisible to a getter property. However, the IsVisible does not automatically change the UI unless I refresh the page.
The following is the code snippet:
xaml:
<StackLayout>
<Picker Title="Choose..." ItemsSource="{Binding MyItemOptions}" SelectedItem="{Binding MyItem}"/>
<Label Text="MyItem is Milk" IsVisible="{Binding Path=IsMilkItem}"/>
</StackLayout>
C#:
private string _myItem;
public string MyItem
{
get => _myItem;
set
{
SetValue(ref _myItem, value);
}
}
public bool IsMilkItem
{
get { return MyItem == "Milk"; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
bool response = false;
if (field == null ||
!field.Equals(value))
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
response = true;
}
return response;
}
The boolean property is updated in code after updating MyItem. However, the UI element does not automatically show up until i manually re-render the page.
I will be grateful if someone has a solution to this.
when you change the value of MyItem, it does not raise a PropertyChanged event for IsMilkItem, so the UI is never told to update
public string MyItem
{
get => _myItem;
set
{
SetValue(ref _myItem, value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsMilkItem"));
}
}
According to your description, you want to choose item from picker, when selecting item is Milk, you want to Label control is visible, Am I right?
If yes, I modify your code, you can take a look:
public partial class Page17 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<string> MyItemOptions { get; set; }
private string _MyItem;
public string MyItem
{
get { return _MyItem; }
set
{
_MyItem = value;
if(_MyItem== "Milk")
{
IsMilkItem = true;
}
else
{
IsMilkItem = false;
}
}
}
private bool _IsMilkItem;
public bool IsMilkItem
{
get { return _IsMilkItem; }
set
{
_IsMilkItem = value;
RaisePropertyChanged("IsMilkItem");
}
}
public Page17 ()
{
InitializeComponent ();
MyItemOptions = new ObservableCollection<string>()
{
"item 1",
"item 2",
"item 3",
"Milk"
};
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Update:
You can use Label trigger to set label isvisible, and don't need to IsMilkItem property to set Lable Isvisible.
<Picker x:Name="picker1" Title="Choose..." ItemsSource="{Binding MyItemOptions}" SelectedItem="{Binding MyItem}" />
<Label Text="MyItem is Milk" IsVisible="False" >
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding Source={x:Reference picker1},Path=SelectedItem}" Value="Milk">
<Setter Property="IsVisible"
Value="True"/>
</DataTrigger>
</Label.Triggers>
</Label>
Here is the .cs:
public ObservableCollection<string> MyItemOptions { get; set; }
private string _MyItem;
public string MyItem
{
get { return _MyItem; }
set
{
_MyItem = value;
}
}
public Page17 ()
{
InitializeComponent ();
MyItemOptions = new ObservableCollection<string>()
{
"item 1",
"item 2",
"item 3",
"Milk"
};
this.BindingContext = this;
}
Related
The code below executes fine when MyActionFunc is called but not when the function is in another class. MessageBox displays the correct string but it is not shown on view. What I am missing?
class ViewModel : INotifyPropertyChanged
{
public MyCommand ActionCommand
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
public ViewModel()
{
ActionCommand = new MyCommand();
ActionCommand.CanExecuteFunc = obj => true;
// ActionCommand.ExecuteFunc = MyActionFunc;
ActionCommand.ExecuteFunc = MyClass.MyActionFunc;
}
private string myname;
public string myName
{
get => myname;
set { myname = value;; OnPropertyChanged(); }
}
public void MyActionFunc(object parameter)
{
myName = "Fred";
}
}
class MyClass
{
public static void MyActionFunc(object parameter)
{
ViewModel name = new ViewModel();
name.myName = "Fred";
MessageBox.Show(name.myName);
}
}
... and the binding to the Textbox
<TextBox Name="textBox" Grid.Column="1" Grid.Row="1" Text="{Binding Path=myName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
I am new to Xamarin but i tried to use BindingContext to set image path
First i tried with
private string _imagePath;
public string ImagePath
{
get
{
return _imagePath;
}
set
{
if (_imagePath != value)
{
_imagePath = value;
OnPropertyChanged();
}
}
}
.
.
.
ImagePath = "TriangleSide_A.png";
.
.
.
<Image Source="{Binding ImagePath}" HeightRequest="300" WidthRequest="300"/>
But no luck then i tried with Auto Property
public string ImagePath {get;set;}
Thats work only with
public string ImagePath {get;} = "TriangleSide_A.png";
According to your description, I don't know how you implement INotifyPropertyChanged interface, generally, I do like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The RaisePropertyChanged likes your OnPropertyChanged method, with the PropertyName that has changed. If we go to our property, we now need to update it, to raise this event, every time the property is changed.
From your code, you don't add propertyname in your OnPropertyChanged, so the ImagePath can not be updated.
Please take a look the following code:
<StackLayout>
<Image
HeightRequest="300"
Source="{Binding ImagePath}"
WidthRequest="300" />
<Button
x:Name="btn1"
Clicked="Btn1_Clicked"
Text="change image source" />
</StackLayout>
public partial class Page32 : ContentPage, INotifyPropertyChanged
{
private string _imagePath;
public string ImagePath
{
get { return _imagePath; }
set
{
if (_imagePath != value)
{
_imagePath = value;
RaisePropertyChanged("ImagePath");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public Page32()
{
InitializeComponent();
ImagePath = "a11.jpg";
this.BindingContext = this;
}
private void Btn1_Clicked(object sender, EventArgs e)
{
ImagePath = "a12.jpg";
}
}
Update:
If you want to use binding in mvvm mode, I do some code that you can take a look:
This is ImageOnClick model, contain some properties.
public class ImageOnClick:ViewModelBase
{
private string _imagePath;
public string ImagePath
{
get { return _imagePath; }
set
{
if (_imagePath != value)
{
_imagePath = value;
RaisePropertyChanged("ImagePath");
}
}
}
}
Now binding this model to contentpage
public partial class Page32 : ContentPage
{
private ImageOnClick imagemodel;
public Page32()
{
InitializeComponent();
imagemodel = new ImageOnClick() { ImagePath = "a11.jpg" };
this.BindingContext = imagemodel;
}
private void Btn1_Clicked(object sender, EventArgs e)
{
imagemodel.ImagePath = "a12.jpg";
}
}
About mvvm binding, you can also take a look:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm
Your Initial code is correct, but you can set the _imagePath to Auto Property like so:
private string _imagePath { get; set; }
public string ImagePath
{
get
{
return _imagePath;
}
set
{
if (_imagePath != value)
{
_imagePath = value;
OnPropertyChanged();
}
}
}
The reason
public string ImagePath {get;set;}
doesn't work is because you need to have the OnPropertyChanged() in the setter.
I am going to create a ComboBox with availability of add item manually by user in WPF. So I have created some code like this:
My View code:
<ComboBox SelectedItem="{Binding Path=SelectedItem}" ItemsSource="{Binding ItemsSource}" Text="{Binding Path=InputText, UpdateSourceTrigger=LostFocus}" IsEditable="True"/>
My ViewModel code:
public class ViewModel : INotifyPropertyChanged
{
private string selectedIndex;
private string inputText;
public event PropertyChangedEventHandler PropertyChanged;
public string InputText
{
get { return inputText; }
set { inputText = value; OnPropertyChanged(); CheckAndInsertIfValid(); }
}
public string SelectedItem
{
get { return selectedIndex; }
set { selectedIndex = value; OnPropertyChanged(); }
}
public ObservableCollection<string> ItemsSource { get; set; }
public ViewModel()
{
ItemsSource = new ObservableCollection<string>()
{
"0", "1", "2", "3", "4" ,"5"
};
SelectedItem = ItemsSource[3];
}
public virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void CheckAndInsertIfValid()
{
if (InputText != "Some Values" && !ItemsSource.Contains(InputText))
ItemsSource.Add(InputText);
}
}
It works fine and user can add to ComboBox manually. But when view is showing to user SelectedItem will be "null" however I've set.
I don't know why SelectedItem is going to be null? And How can I prevent to change of SelectedItem?
The InputText property in your case doesn't seem necessary to me, you can get rid of it and bind directly to the SelectedItem property:
<ComboBox SelectedItem="{Binding Path=SelectedItem}" ItemsSource="{Binding ItemsSource,Mode=TwoWay}" Text="{Binding Path=SelectedItem, UpdateSourceTrigger=LostFocus}" IsEditable="True"/>
And change your VM accourdingly:
public class ViewModel : INotifyPropertyChanged
{
private string _selectedItem;
public event PropertyChangedEventHandler PropertyChanged;
public string SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value; OnPropertyChanged(); CheckAndInsertIfValid(); }
}
public ObservableCollection<string> ItemsSource { get; set; }
public ViewModel()
{
ItemsSource = new ObservableCollection<string>()
{
"0", "1", "2", "3", "4" ,"5"
};
SelectedItem = ItemsSource[3];
}
public virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void CheckAndInsertIfValid()
{
if (SelectedItem != "Some Values" && !ItemsSource.Contains(SelectedItem))
ItemsSource.Add(SelectedItem);
}
}
I have model Names with 2 fields.
public class Names
{
public string ID { get; set; }
public string Name { get; set; }
}
I need to get all names from my model Names to picker in XAMARIN.
<Picker Title="Select a name"
ItemsSource="{Binding AllNames}"
ItemDisplayBinding="{Binding Name}" />
What is the most simple way to do it?
You would want to create an ObservableCollection with the object you want to use inside your list inside your view model.
Like this:
public class ViewModel
{
ObservableCollection<Names> allNames = new ObservableCollection<GroupedReportModel>();
public ObservableCollection<Names> AllNames
{
get { return allNames; }
set { SetProperty(ref allNames, value); }
}
}
The SetProperty is an override you will get by adding an implementation of INotifyPropertyChanged onto your viewModel.
The code I use for that looks like this:
protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName]string propertyName = "", Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I have ViewModel(implemented INotifyPropertyChanged) in the background and class Category which has only one property of type string. My ComboBox SelectedItem is bind to an instance of a Category. When i change the value of instance, SelectedItem is not being updated and Combobox is not changed.
EDIT: code
Combobox:
<ComboBox x:Name="categoryComboBox" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="2"
Margin="10" ItemsSource="{Binding Categories}"
DisplayMemberPath="Name" SelectedValue="{Binding NodeCategory, Mode=TwoWay}"/>
Property:
private Category _NodeCategory;
public Category NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
[Serializable]
public class Category : INotifyPropertyChanged
{
private string _Name;
[XmlAttribute("Name")]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
}
and what I am trying is: when I set
NodeCategory = some_list_of_other_objects.Category;
to have that item selected in Combobox with appropriate DisplayMemberPath
The category you are setting in this line -
NodeCategory = some_list_of_other_objects.Category;
and one present in your Categories collection(ItemsSource="{Binding Categories}") should be referring to same object. If they are not then SelectedItem won't work.
Solution 1 -
You can also try to use SelectedValuePath like this -
<ComboBox x:Name="categoryComboBox"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />
and in code you can do something like this -
private string _NodeCategory;
public string NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
and set selected item like this -
NodeCategory = some_list_of_other_objects.Category.Name;
and use selected value like this -
Category selectedCategory =
some_list_of_other_objects.FirstOrDefault(cat=> cat.Name == NodeCategory);
or
Category selectedCategory =
Categories.FirstOrDefault(cat=> cat.Name == NodeCategory);
Solution 2 -
Another possible solution can be -
NodeCategory =
Categories.FirstOrDefault(cat=> cat.Name == some_list_of_other_objects.Category.Name);
this way your NodeCategory property will have the reference of an object in Categories collection and SelectedItem will work.
Your XAML needs a couple of modifications but I think the real problem is with the code you have posted which I don't think is telling the full story.
For starters, your combobox ItemSource is bound to a property called Categories but you do not show how this property is coded or how your NodeCategory property is initially synced with the item.
Try using the following code and you will see that the selected item is kept in sync as the user changes the value in the combobox.
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ComboBox x:Name="categoryComboBox"
Grid.Column="1"
Grid.Row="3"
Grid.ColumnSpan="2"
Margin="10"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedItem="{Binding NodeCategory}" />
<Label Content="{Binding NodeCategory.Name}" />
</StackPanel>
Code-behind
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Category> _categories = new ObservableCollection<Category>
{
new Category { Name = "Squares"},
new Category { Name = "Triangles"},
new Category { Name = "Circles"},
};
public MainWindow()
{
InitializeComponent();
NodeCategory = _categories.First();
this.DataContext = this;
}
public IEnumerable<Category> Categories
{
get { return _categories; }
}
private Category _NodeCategory;
public Category NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
[Serializable]
public class Category : INotifyPropertyChanged
{
private string _Name;
[XmlAttribute("Name")]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
}
From my little example:
Note: This is setting just a string (or a category from another list), but the basics should be same here:
Basically this is done:
private void button1_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ComboBoxSampleViewModel).SelectCategory("Categorie 4");
}
Here is my XAML:
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="76,59,0,0"
Name="comboBox1" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding List.Categories}"
DisplayMemberPath="Name"
SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />
<Button Content="Button" Height="27" HorizontalAlignment="Left"
Margin="76,110,0,0" Name="button1" VerticalAlignment="Top"
Width="120" Click="button1_Click" />
</Grid>
and in the ViewModel of the Window
class ComboBoxSampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public CategoryList List { get; set; }
public ComboBoxSampleViewModel()
{
this.List = new CategoryList();
NodeCategory = List.Selected;
}
private ComboBoxSampleItemViewModel nodeCategory;
public ComboBoxSampleItemViewModel NodeCategory
{
get
{
return nodeCategory;
}
set
{
nodeCategory = value;
NotifyPropertyChanged("NodeCategory");
}
}
internal void SelectCategory(string p)
{
this.List.SelectByName(p);
this.NodeCategory = this.List.Selected;
}
}
With the help of this little class:
public class CategoryList
{
public ObservableCollection<ComboBoxSampleItemViewModel> Categories { get; set; }
public ComboBoxSampleItemViewModel Selected { get; set; }
public CategoryList()
{
Categories = new ObservableCollection<ComboBoxSampleItemViewModel>();
var cat1 = new ComboBoxSampleItemViewModel() { Name = "Categorie 1" };
var cat2 = new ComboBoxSampleItemViewModel() { Name = "Categorie 2" };
var cat3 = new ComboBoxSampleItemViewModel() { Name = "Categorie 3" };
var cat4 = new ComboBoxSampleItemViewModel() { Name = "Categorie 4" };
Categories.Add(cat1);
Categories.Add(cat2);
Categories.Add(cat3);
Categories.Add(cat4);
this.Selected = cat3;
}
internal void SelectByName(string p)
{
this.Selected = this.Categories.Where(s => s.Name.Equals(p)).FirstOrDefault();
}
}
And this Item ViewModel
public class ComboBoxSampleItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
If Combobox is bound to object class of the View Model, while the SelectionBoxItem of the sender object (in the SelectionChanged in code behind) has not that type, it means it's still loading.
ComboBox combo = sender as ComboBox;
if (combo.SelectionBoxItem.GetType() == typeof(BindedClass))
{
// Not loading
}