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"));
}
Related
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);
});
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);
}
I'm trying to create a treeview with an ObservableCollection, but I'm having a problem on obtaining the elements from a list in my object. What happens is that I can retrieve the group name, it displays on the list just fine, but then instead of getting the usernames I get a blank space. Here's some code:
<TreeView x:Name="treeUsers">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=GroupName}" >
<Grid Margin="1">
<CheckBox Content="{Binding Path=UserList.Name}" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
In this code, I obtain the users from a list, then separate them into different groups, and return the list to my Viewer.
private Dictionary<string, GroupDTO> _groupsDTO= new Dictionary<string, GroupDTO>();
_groupsDTO.Add("GENERAL", new GroupDTO("GENERAL"));
foreach (NetSendUser user in ObtainUserList())
{
UserDTO userDTO = new UserDTO (user);
_groupsDTO["GENERAL"].UserList.Add(userDTO );
if (!_groupsDTO.ContainsKey(user.Area))
_groupsDTO.Add(user.Area, new GroupDTO(user.Area));
_groupsDTO[user.Area].UserList.Add(userDTO);
}
ObservableCollection<GroupDTO> groups= new ObservableCollection<GroupDTO>(_groupDTO.Values.ToArray());
treeUsers.ItemsSource = groups;
And here's my GroupDTO:
public class GroupDTO: ObjectDTO
{
internal GroupDTO(string name)
: base()
{
_groupName = name;
}
private string _groupName = default(string);
public string GroupName
{
get { return _groupName ; }
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
private ObservableCollection<UserDTO> _userList= new ObservableCollection<UserDTO>();
public ObservableCollection<FuncionarioDTO> UserList
{
get { return _userList; }
}
}
I'm not even sure how your application is working at all. You are binding GroupName (property of type string) to an ItemsSource (which requires collection). And then you are trying to bind to UserList.Name, but it is a collection of items, it doesn't expose Name property, User does. Here is a simplified working example.
Xaml:
<Window x:Class="WpfTestBench.TreeSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfTestBench="clr-namespace:WpfTestBench"
Title="Tree sample" Height="300" Width="300">
<TreeView ItemsSource="{Binding Groups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="wpfTestBench:Group" ItemsSource="{Binding Users}">
<Label Content="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="wpfTestBench:User">
<CheckBox Content="{Binding Path=Username}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Codebehind:
using System.Collections.Generic;
namespace WpfTestBench
{
public partial class TreeSample
{
public TreeSample()
{
InitializeComponent();
DataContext = new Context();
}
}
public class Context
{
public Context()
{
Groups = new List<Group>();
var mainGroup = new Group
{
Name = "Main",
Users = new List<User> { new User("John"), new User("Bill") }
};
var secondaryGroup = new Group
{
Name = "Secondary",
Users = new List<User> { new User("Tom"), new User("Phil") }
};
Groups.Add(mainGroup);
Groups.Add(secondaryGroup);
}
public IList<Group> Groups { get; private set; }
}
public class Group
{
public string Name { get; set; }
public IList<User> Users { get; set; }
}
public class User
{
public User(string name)
{
Username = name;
}
public string Username { get; private set; }
}
}
Execution result:
I am currently implementing the application that displays hierarchy using ListBoxes (please do not suggest using TreeView, ListBoxes are needed).
It looks like that in the article: WPF’s CollectionViewSource (with source code).
Classes:
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
}
public class Lift
{
public ObservableCollection<string> Runs { get; }
}
The example uses CollectionViewSource instances (see XAML) to simplify the design.
An instance of Mountains class is the DataContext for the window.
The problem is: I would like that the Mountains class to have SelectedRun property and it should be set to currently selected run.
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
public string SelectedRun { get; set; }
}
Maybe I've missed something basic principle, but how can I achieve this?
You may want to read about the use of '/' in bindings. See the section 'current item pointers' on this MSDN article.
Here's my solution:
Xaml
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="0" Text="Mountains"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="1" Text="Lifts"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="2" Text="Runs"/>
<ListBox Grid.Row="1" Grid.Column="0" Margin="5"
ItemsSource="{Binding Mountains}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
<ListBox Grid.Row="1" Grid.Column="1" Margin="5"
ItemsSource="{Binding Mountains/Lifts}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True"/>
<ListBox Grid.Row="1" Grid.Column="2" Margin="5"
ItemsSource="{Binding Mountains/Lifts/Runs}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedRun}"/>
</Grid>
C# (note, you don't need to implement INotifyPropertyChanged unless the properties will be changed and not just selected)
public class MountainsViewModel
{
public MountainsViewModel()
{
Mountains = new ObservableCollection<Mountain>
{
new Mountain
{
Name = "Whistler",
Lifts = new ObservableCollection<Lift>
{
new Lift
{
Name = "Big Red",
Runs = new ObservableCollection<string>
{
"Headwall",
"Fisheye",
"Jimmy's"
}
},
new Lift
{
Name = "Garbanzo",
Runs = new ObservableCollection<string>
{
"Headwall1",
"Fisheye1",
"Jimmy's1"
}
},
new Lift {Name = "Orange"},
}
},
new Mountain
{
Name = "Stevens",
Lifts = new ObservableCollection<Lift>
{
new Lift {Name = "One"},
new Lift {Name = "Two"},
new Lift {Name = "Three"},
}
},
new Mountain {Name = "Crystal"},
};
}
public string Name { get; set; }
private string _selectedRun;
public string SelectedRun
{
get { return _selectedRun; }
set
{
Debug.WriteLine(value);
_selectedRun = value;
}
}
public ObservableCollection<Mountain> Mountains { get; set; }
}
public class Mountain
{
public string Name { get; set; }
public ObservableCollection<Lift> Lifts { get; set; }
}
public class Lift
{
public string Name { get; set; }
public ObservableCollection<string> Runs { get; set; }
}
Here's how I would do it. You want to make sure that you fire the INotifyPropertyChanged event when setting the properties. To get the Selected Run you'll have to get MainViewModel.SelectedMountain.SelectedLift.SelectedRun.
public class MainViewModel: ViewModelBae
{
ObservableCollection<MountainViewModel> mountains
public ObservableCollection<MountainViewModel> Mountains
{
get { return mountains; }
set
{
if (mountains != value)
{
mountains = value;
RaisePropertyChanged("Mountains");
}
}
}
MountainViewModel selectedMountain
public MountainViewModel SelectedMountain
{
get { return selectedMountain; }
set
{
if (selectedMountain != value)
{
selectedMountain = value;
RaisePropertyChanged("SelectedMountain");
}
}
}
}
public class MountainViewModel: ViewModelBae
{
ObservableCollection<LiftViewModel> lifts
public ObservableCollection<LiftViewModel> Lifts
{
get { return lifts; }
set
{
if (lifts != value)
{
lifts = value;
RaisePropertyChanged("Lifts");
}
}
}
LiftViewModel selectedLift
public LiftViewModel SelectedLift
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
public class LiftViewModel: ViewModelBae
{
ObservableCollection<string> runs
public ObservableCollection<string> Runs
{
get { return runs; }
set
{
if (runs != value)
{
runs = value;
RaisePropertyChanged("Runs");
}
}
}
string selectedRun
public string SelectedRun
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
<ListBox ItemsSource="{Binding Mountains}" SelectedItem="{Binding SelectedMountain, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.Lifts}" SelectedItem="{Binding SelectedMountain.SelectedLift, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.SelectedLift.Runs}" SelectedItem="{Binding SelectedMountain.SelectedLift.SelectedRun, Mode=TwoWay}">
Your ViewModel should not also be a collection, it should contain collections and properties which are bound to the view. SelectedRun should be a property of this ViewModel (MountainViewModel) not Mountains. MountainViewModel should expose the Mountains collection and SelectedRun and should be bound to the listboxes' ItemsSource and SelectedItem.