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);
}
Related
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"));
}
I have an ItemTemplateSelector which contains Multiple DataTemplates which have Different DataTypes.
I thus have multiple ItemSources based on Module Selected.
How to bind my ListView with multiple ItemSources based on the module selected?
Explanation:
1)ViewModel_A is my ItemSource and DataTemplateA is my DataTemplate when my Module A is Selected
2)ViewModel_B is my ItemSource DataTemplateB is my DataTemplate when my Module B is Selected
I tried Implementing a BaseViewModel and tried binding the BaseViewModel Type in my ItemSource But this doesn't allow the access of derived class properties.
How to Dynamically Select My ItemSource?
Step 1
First Create a UserControl which contains your ListView in your Xaml and two DependancyProperty for ItemSource and DataTemplate
DataList.Xaml
<UserControl
x:Class="MultipleDataTemplate.DataList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<ListView ItemsSource="{x:Bind ItemsSource,Mode=OneWay}"></ListView>
</Grid>
</UserControl>
DataList.xaml.cs
public sealed partial class DataList : UserControl
{
public DataList()
{
this.InitializeComponent();
}
#region ItemsSource
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(DataList), new PropertyMetadata(null));
#endregion
#region ItemTemplate
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(DataList), new PropertyMetadata(null));
#endregion
}
Step 2
Now you can you this usercontrol with any multiple DataTemplate's and multiple itemsource as below
MainPage.xaml
<Page
x:Class="MultipleDataTemplate.Cars"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:MultipleDataTemplate">
<Page.Resources>
<DataTemplate x:Key="CarKey" x:DataType="controls:Car">
<Grid>
<TextBlock Text="{x:Bind carprop1}"></TextBlock>
<TextBlock Text="{x:Bind carprop2}"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="BikeKey" x:DataType="controls:Bike">
<Grid>
<TextBlock Text="{x:Bind Bikeprop1}"></TextBlock>
<TextBlock Text="{x:Bind Bikeprop2}"></TextBlock>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<controls:DataList ItemsSource="{x:Bind ItemSource,Mode=OneWay}" ItemTemplate="{x:Bind ItemTemplate}"></controls:DataList>
<StackPanel>
<Button Content="Cars" Click="CarsClick"/>
<Button Content="Bike" Click="BikeClick"/>
</StackPanel>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class Cars : Page, INotifyPropertyChanged
{
public object _ItemSource { get; set; }
public object ItemSource
{
get { return _ItemSource; }
set
{
_ItemSource = value;
this.OnPropertyChanged();
}
}
public DataTemplate _itemTemplate { get; set; }
public DataTemplate ItemTemplate
{
get { return _itemTemplate; }
set
{
_itemTemplate = value;
this.OnPropertyChanged();
}
}
public Cars()
{
this.InitializeComponent();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private void CarsClick(object sender, RoutedEventArgs e)
{
ItemSource = new List<Car>() { new Car() { carprop1 = "1", carprop2 = "2" } };
ItemTemplate = this.Resources["CarKey"] as DataTemplate;
}
private void BikeClick(object sender, RoutedEventArgs e)
{
ItemSource = new List<Bike>() { new Bike() { Bikeprop1 = "1", Bikeprop2 = "2" } };
ItemTemplate = this.Resources["BikeKey"] as DataTemplate;
}
}
public class Car
{
public string carprop1 { get; set; }
public string carprop2 { get; set; }
}
public class Bike
{
public string Bikeprop1 { get; set; }
public string Bikeprop2 { get; set; }
}
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));
}
}
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);
});
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}"