Is there any way to change menu selected item from ViewModel? - c#

I'm trying to change menu selected item from my ViewModel in my Xamarin.Forms application. How can I change that?
I've bind ListView SelectedItem property to field in my ViewModel, with mode "TwoWay". Also I used BeginInvokeOnMainThread and ContinueWith.
To bind an event I created a behavior and bind Command to event.
All the ways didn't change selected item.
<ListView
x:Name="ListViewMenu"
HasUnevenRows="True"
ItemsSource="{Binding menuItems}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
Command="{Binding command}"
Converter="{StaticResource SelectedItemConverter}"
EventName="ItemSelected" />
</ListView.Behaviors>
private HomeMenuItem selectedItem { get; set; }
public HomeMenuItem SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
this.OnPropertyChanged();
}
}
command = new AsyncRelayCommand((sender) => this.ItemSelected(sender).ContinueWith((arg) =>
{
HomeMenuItem menuItem = sender as HomeMenuItem;
if(menuItem.Id != SelectedItem.Id)
Device.BeginInvokeOnMainThread(() =>
{
SelectedItem = menuItems.Where(s => s.Id.Equals(menuItem.Id)).FirstOrDefault();
});
}));
I expected to change selected item, from item Id 2 to Id 0 but this always stay on Id 2, even if SelectedItem variable is changed to Id 0. I mean visual representation don't change.

I wrote a simple demo to update the menu selected item, you could refer to it.When I click the button the listview selectedItem will reback to the first one whatever I click the which one
There is MainPage.xml
<StackLayout>
<Button Command="{Binding UpdateCommand}"></Button>
<ListView
x:Name="ListViewMenu"
HasUnevenRows="True"
ItemsSource="{Binding menuItems}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
MainPage.xml.cs
public partial class MainPage : ContentPage
{
public MainPageModelView model { get; set; }
public MainPage()
{
InitializeComponent();
model = new MainPageModelView();
BindingContext = model;
}
}
MainPageModelView.cs
public class MainPageModelView: INotifyPropertyChanged
{
public ObservableCollection<HomeMenuItem> menuItems { set; get; }
public event PropertyChangedEventHandler PropertyChanged;
public MainPageModelView()
{
menuItems = new ObservableCollection<HomeMenuItem>();
menuItems.Add(new HomeMenuItem() { Name = "Mr. Mono1", Id = 1 });
menuItems.Add(new HomeMenuItem() { Name = "Mr. Mono2", Id = 2 });
menuItems.Add(new HomeMenuItem() { Name = "Mr. Mono3", Id = 3 });
menuItems.Add(new HomeMenuItem() { Name = "Mr. Mono4", Id = 4 });
}
private HomeMenuItem selectedItem { get; set; }
public HomeMenuItem SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
this.OnPropertyChanged();
}
}
Command _updateCommand;
public Command UpdateCommand
{
get
{
return _updateCommand ?? (_updateCommand = new Command(ExecuteSaveCommand));
}
}
void ExecuteSaveCommand()
{
Device.BeginInvokeOnMainThread(() =>
{
SelectedItem = menuItems[0];
});
//SelectedItem = menuItems.Where(s => s.Id.Equals(menuItem.Id)).FirstOrDefault();
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
}

Related

TabControl the variable changes in all TabItems

I have a list(TabItemViewModel) which are binding to TabControl to generate TabItems and inside TabItemViewModel class I have second list(LanguageTexts) with some strings. But when I change variable value in anyone element in class LanguageTexts hViewModel.Items[0].languageTexts[0].ownedVersion = "test"; this are changing in all tabs, but I want only to change in one particular tab.
XAML:
<TabControl ItemsSource="{Binding Path=Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=languageTexts[0].ownedVersion}" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" FontSize="18"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
C#
public class TabControlViewModel
{
public ObservableCollection<TabItemViewModel> Items { get; set; } = new ObservableCollection<TabItemViewModel>
{
new TabItemViewModel {Name="Tab 1", IsSelected = true },
new TabItemViewModel {Name="Tab 2" },
new TabItemViewModel {Name="Tab 3" },
new TabItemViewModel {Name="Tab 4" },
};
}
public class TabItemViewModel
{
public ObservableCollection<LanguageTexts> languageTexts { get; private set; } = new ObservableCollection<LanguageTexts>();
public string Name { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
DoSomethingWhenSelected();
}
}
private void DoSomethingWhenSelected()
{
if (isSelected)
Debug.WriteLine("You selected " + Name);
}
}
public class LanguageTexts : INotifyPropertyChanged
{
private string _ownedVersion;
public string ownedVersion
{
get
{
return _ownedVersion;
}
set
{
if (value != _ownedVersion)
{
_ownedGameVersionTXT = value;
OnPropertyChanged();
}
}
}
public MainWindow()
{
InitializeComponent();
LanguageTexts languageTexts = new LanguageTexts("en_US");
foreach (var item in hViewModel.Items)
{
item.languageTexts.Add(languageTexts);
}
Since LanguageTexts is a class, each reference in your view models reference the same texts (class is a reference type). For each view to have its own copy, you would need to make a new copy for each view.
foreach (var item in hViewModel.Items)
{
item.languageTexts.Add(new LanguageTexts("en_US"));
}

ComboBox SelectedItem not getting set when using custom ItemTemplate

I have a ComboBox with custom ItemTemplate, it has a CheckBox which is binding to a ViewModel of that page.
What I am trying to do is to set ComboBox item selected to the first checked value whenever ComboBox is closed, but the value is not getting set accordingly.
Is there anything that I am missing?
Xaml:
<ComboBox x:Name="myComboBox" ItemsSource="{Binding ComboBoxList}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" Height="50" Width="150">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content="{Binding}" Margin="0" />
</DataTemplate>
</ComboBox.ItemTemplate>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsDropDownOpen, ElementName=myComboBox}" ComparisonCondition="NotEqual" Value="True">
<core:InvokeCommandAction Command="{Binding DropDownClosedCommand}"/>
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private string header;
public string Header
{
get { return header; }
set
{
header = value;
RaisePropertyChange(nameof(Header));
}
}
private Person selectedPerson;
public Person SelectedPerson
{
get { return selectedPerson; }
set { selectedPerson = value; RaisePropertyChange(nameof(SelectedPerson)); }
}
private ObservableCollection<Person> comboBoxList;
public ObservableCollection<Person> ComboBoxList
{
get { return comboBoxList; }
set { comboBoxList = value; }
}
public DelegateCommand DropDownClosedCommand { get; set; }
public MainViewModel()
{
Header = "My Header";
ComboBoxList = new ObservableCollection<Person> {
new Person() { Name = "Person 1", IsChecked = false },
new Person() { Name = "Person 2", IsChecked = false },
new Person() { Name = "Person 3", IsChecked = false },
new Person() { Name = "Person 4", IsChecked = false }
};
DropDownClosedCommand = new DelegateCommand(OnDropDownClosed);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnDropDownClosed(object e)
{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
}
}
public class Person
{
public string Name { get; set; }
public bool IsChecked { get; set; }
public override string ToString()
{
return Name;
}
}
Finally, I figured out the issue that was preventing ComboBox to set SelectedItem. It was because SelectedPerson was not getting set on UI thread, so I wrap that code in dispatcher and it's working as expected.
Code:
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
});

How to get access to SelectedItem in Nested Listbox

I'm trying to get access to the SelectedItem and also to the Command binded to a nested ListBox inside an ItemsControl. Below is the simplified XAML.
<ItemsControl ItemsSource="{Binding Collection}"
ItemTemplate="{StaticResource Partials}"
</ItemsControl>
<DataTemplate x:Key="Partials">
<ListBox ItemsSource="{Binding ListBoxItems}"
SelectedItem="{Binding SelectedItem}" >
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteSelectedItemCommand/>
</ListBox.InputBindings>
</ListBox>
</DataTemplate>
This is what I have in the VM (simplified)
private void FillList()
{
//Populate the list
Collection.Add(Data);
var ListBoxViewSource = new CollectionViewSource { Source = ListBoxDataSource};
Collection.ListBoxItems= ListBoxViewSource.View;
}
private ObservableCollection<Scene> _collection= new ObservableCollection<Scene>();
public ObservableCollection<Scene> Collection
{
get { return _collection; }
set
{
if (value != _collection)
{
_collection= value; RaisePropertyChanged();
}
}
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set { _selectedItem= value; RaisePropertyChanged(); }
}
public RelayCommand DeleteSelectedItemCommand { get; private set; }
private void DeleteSelectedItem()
{
SourceData.Remove(SelectedItem);
}
How can get the value of the SelectedItem in the listbox?
To get SelectedItem, you have to use a property in your DataContext class.
You are using ListBoxes as items, so you have to declare a property in your inner DataContext.
You can also use RoutedEvent ( ListBox.SelectionChanged ) at ItemsControl level to get SelectedValue from ListBox.
Sample :
ViewModel
public class StateVM
{
public List<CountryStates> Collection { get; set; }
public StateVM() { Collection = new List<CountryStates>(); }
}
public class CountryStates
{
public string SelectedState { get; set; } /* SelectedItem will arrive here */
public string Name { get; set; }
public List<string> States { get; set; }
}
XAML
<DataTemplate x:Key="Partials">
<ListBox ItemsSource="{Binding States}" SelectedValue="{Binding SelectedState}">
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteSelectedItemCommand}"/>
</ListBox.InputBindings>
</ListBox>
</DataTemplate>
<ItemsControl x:Name="CountryStateList" ItemsSource="{Binding Collection}"
ListBox.SelectionChanged="ListBox_SelectionChanged"
ItemTemplate="{StaticResource Partials}">
</ItemsControl>
CodeBehind
StateVM vm = new StateVM();
vm.Collection.Add(new CountryStates() { Name = "India", States = new[] { "mp", "up", "tn" }.ToList() });
vm.Collection.Add(new CountryStates() { Name = "USA", States = new[] { "new york", "washington" }.ToList() });
CountryStateList.DataContext = vm;
...
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox source = (ListBox)(e.OriginalSource);
System.Diagnostics.Debug.WriteLine(">>>>>> " + ((CountryStates)(source.DataContext)).SelectedState);
}

How to bind button to Listbox item

ViewModel
public class MainWindowViewModel:BindableBase
{
public IRelayCommand MyCommand { get; protected set; }
private void CreateCommand()
{
this.MyCommand = new RelayCommand(MyCommandExecuted, CanExecuteMyCommand);
}
private void MyCommandExecuted(object obj)
{
MessageBox.Show("Command Executed");
}
private bool CanExecuteMyCommand(object obj)
{
return true; // The value is based on Selected Item
}
}
XAML
<ListBox
x:Name="myListBox"
ItemsSource="{Binding Path=MyClass}"
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Path=HeaderName}" IsExpanded="True">
<StackPanel>
<DataGrid
x:Name="dataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=RowVal}" SelectedItem="{Binding CurrentItem}"/>
</StackPanel>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
<Button Content="Select"
Command="{Binding Path=MyCommand }"
CommandParameter="{Binding ElementName=myListBox,Path=SelectedItem}"/>
DataClass
public class DataClass
{
public string HeaderName { get; set; }
public object RowVal { get; set; }
public ObservableCollection<DataGridColumn> ColumnCollection { get; set;}
private object currentItem;
public object CurrentItem
{
get
{
return currentItem;
}
set
{
currentItem = value;
}
}
}
How can I bind my button to Listbox item which is CurrentItem in DataClass ?
I created a complete example to show how I would do it. You would have to bind the parent element to SelectedItem as well, to keep track of when that item changes. Since the SelectedItem is public in your child class as well you can access that when your command triggers in your main view model.
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Parents}" SelectedItem="{Binding SelectedParent}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parent}">
<DataGrid ItemsSource="{Binding Children}" SelectedItem="{Binding SelectedChild}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Width="70" Content="Click me" Height="25" Command="{Binding MyCommand}" />
i.e. in DoWork, you can get the child from the parent via its public property.
public sealed class WindowViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<Parent> parents;
private readonly ICommand myCommand;
private Parent selectedParent;
public WindowViewModel()
{
parents = new ObservableCollection<Parent>
{
new Parent{ Name = "P1"},
new Parent{ Name = "P2"}
};
myCommand = new DelegateCommand(DoWork);
}
private void DoWork()
{
var selectedChild = SelectedParent == null ? null : SelectedParent.SelectedChild;
}
public Parent SelectedParent
{
get { return selectedParent; }
set
{
if (selectedParent == value)
return;
selectedParent = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Parent> Parents
{
get { return parents; }
}
public ICommand MyCommand
{
get { return myCommand; }
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
With the basic setup of Data models
public class Parent : INotifyPropertyChanged
{
private ObservableCollection<Child> children;
private Child m_SelectedChild;
public Parent()
{
children = new ObservableCollection<Child>
{
new Child {Name = "C1"},
new Child {Name = "C2"}
};
}
public string Name { get; set; }
public ObservableCollection<Child> Children
{
get { return children; }
}
public Child SelectedChild
{
get { return m_SelectedChild; }
set
{
if (m_SelectedChild == value)
return;
m_SelectedChild = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Child
{
public string Name { get; set; }
}
Another solution, if you are more interested of the Child item in your WindowViewModel is to change the relative source of where the binding should occur, in your DataGrid. i.e., the binding would look like this instead:
<DataGrid ItemsSource="{Binding Children}"
SelectedItem="{Binding DataContext.SelectedChild, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" />
and then move the Property from Parent to WindowViewModel. With that you would be able to trigger changes to your button command when the child element changes for any of the Parent elements.
I think you want to pass the CurrentItem to the MyCommand as CommandParameter right?
Then you only have to:
CommandParameter="{Binding CurrentItem, UpdateSourceTrigger=PropertyChanged}"
Try this :
CommandParameter="{Binding ElementName=myListBox,Path=SelectedItem.CurrentItem}"

WPF Combobox binding to update multiple textbox

i have a class
class Names {
public int id get; set;};
public string name {get ; set};
public string til {set{
if (this.name == "me"){
return "This is me";
}
}
i have a list (ListNames) which contains Names added to it and binding with a combo box
<ComboBox SelectedValue="{Binding Path=id, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding
ListNames}" DisplayMemberPath="name" SelectedValuePath="id" />
every thing works
i want to display the "Tip" on another label field when user selects an item.
is it possible?
help!
I'm assuming you're using MVVM.
You need to create in your window's viewmodel, a property "CurrentName" of type "Names", binded to the ComboBox SelectedItem property.
This property must raise the NotifyPropertyChanged event.
Then, bind your label field, to this "CurrentName" property.
When the SelectedIem property will change on the combobox, your label field will then be updated.
Something like this:
Your Model:
public class Names
{
public int Id { get; set; }
public string Name { get; set; }
public string Tip {
get
{
return Name == "me" ? "this is me" : "";
}
}
}
Your ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Names> _namesList;
private Names _selectedName;
public ViewModel()
{
NamesList = new ObservableCollection<Names>(new List<Names>
{
new Names() {Id = 1, Name = "John"},
new Names() {Id = 2, Name = "Mary"}
});
}
public ObservableCollection<Names> NamesList
{
get { return _namesList; }
set
{
_namesList = value;
OnPropertyChanged();
}
}
public Names SelectedName
{
get { return _selectedName; }
set
{
_selectedName = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And finally your View:
<Window x:Class="Combo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Combo="clr-namespace:Combo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<Combo:ViewModel/>
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Path=NamesList}" DisplayMemberPath="Name"
SelectedValue="{Binding Path=SelectedName}"/>
<Label Content="{Binding Path=SelectedName.Name}"/>
</StackPanel>

Categories