I created a ListView and set a ObservableCollection to ItemsSource.
Then, I can use a int to binding SelectedIndex, it's work well.
But I don't know how to get item's detail when I selected.
I want to get ObservableCollection, then binding it to show on a TextBlock.
My TextBlock and ListView are difference UserControl. ViewModel in MainWindow.
So I want to know how to get ObservableCollection item by ListView SelectedIndex?
Or others method to resolve it?
Thx.
ViewModel in MainWinodw:
public class TestVM : INotifyPropertyChanged
{
private int _index;
public int Index
{
get
{
return _index;
}
set
{
_index = value;
RaisePropertyChanged("Index");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<User> _items;
public ObservableCollection<User> Items
{
get
{
return _items;
}
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
ObservableCollection<User> _collection;
public ObservableCollection<User> Collection
{
get
{
return _collection;
}
private set
{
_collection = value;
RaisePropertyChanged("Collection");
}
}
ListCollectionView _groupView;
public ListCollectionView GroupView
{
get
{
return _groupView;
}
private set
{
_groupView = value;
RaisePropertyChanged("GroupView");
}
}
public TestVM()
{
Collection = new ObservableCollection<User>();
Collection.Add(new User() { Name = "John Doe1", Age = 10, group = "Group 1" });
Collection.Add(new User() { Name = "Jane Doe2", Age = 20, group = "Group 1" });
Collection.Add(new User() { Name = "Sammy Doe", Age = 30, group = "Group 2" });
Collection.Add(new User() { Name = "Sammy Doe1", Age = 40, group = "Group 2" });
Collection.Add(new User() { Name = "Sammy Doe2", Age = 50, group = "Group 2" });
Collection.Add(new User() { Name = "Sammy Doe3", Age = 60, group = "Group 3" });
Collection.Add(new User() { Name = "Sammy Doe4", Age = 70, group = "Group 3" });
GroupView = new ListCollectionView(Collection);
GroupView.GroupDescriptions.Add(new PropertyGroupDescription("group"));
}
}
public class User
{
public string Name { set; get; }
public int Age { set; get; }
public string group { get; set; }
}
ListView in UserControl1:
<ListView Margin="10" Name="lv" ItemsSource="{Binding GroupView}" SelectedIndex="{Binding Index}">
<ListView.View>
<GridView>
<local:GridViewColumnExt Header="Name" Width="120" DisplayMemberBinding="{Binding Name}"/>
<local:GridViewColumnExt x:Name="colAge" Header="Age" Width="50">
<local:GridViewColumnExt.CellTemplate>
<DataTemplate>
<Button Content="{Binding Age}"></Button>
</DataTemplate>
</local:GridViewColumnExt.CellTemplate>
</local:GridViewColumnExt>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
</GroupStyle>
</ListView.GroupStyle>
</ListView>
TextBlock in UserControl2 (Just want to show item detail) :
<WrapPanel>
<TextBlock Text="SelectdIndex: "/>
<TextBlock Text="{Binding Index}" />
</WrapPanel>
MainWindow.xmal
<Grid>
<Grid.DataContext>
<local:TestVM/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<local:StepList Grid.Column="0"></local:StepList>
<local:ItemDetail Grid.Column="1"></local:ItemDetail>
</Grid>
If you want the details of the selected item in ListView to show up in the Textbox you have to set the binding in the Textbox.
e.g.
Text="{Binding SelectedItem.EnterThePropertyToShowhere, ElementName=EnterTheNameOfyourListViewhere, UpdateSourceTrigger="PropertyChanged"}"
Edit 3: Ok forgett what I said. Try:
Add to your Listview
SelectedItem="{Bindig SelectedUser , UpdateSourceTrigger="PropertyChanged"}"
Add to your ViewModel a property SelectedUser with propertychange Notification
public User SelectedUser{
get { return _selectedUser; }
set
{
if (value == _selectedUser) return;
_selectedUser= value;
RaisePropertyChanged("SelectedUser");
}
}
Add to your Textbox:
Text="{Binding SelectedUser.PropertyWhichShouldShow, UpdateSourceTrigger="PropertyChanged"}"
Related
Goal
I am aiming to alter the selected value for a record and get the new value for a specific column in a DataGrid.
Right now, If I was to change a value in the Name column:
It detects the change:
Problem
When I change the position title, it does not show the new value.
Question
Why does it not detect the new value? And how do I do it?
What I have tried
I have tried to add OnPropertyChanged to all properties (except the override) for both models. This didn't do anything.
Code
Models
public class Person
{
public string Name { get; set; }
public Position Position { get; set; }
}
public class Position
{
public int PositionId { get; set; }
public string PositionTitle { get; set; }
public override bool Equals(object obj) =>
obj is Position p && PositionId == p.PositionId;
public override int GetHashCode() => PositionId.GetHashCode();
}
ViewModel
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<Person> people;
public ObservableCollection<Person> People
{
get { return people; }
set
{
people = value;
OnPropertyChanged();
}
}
private ObservableCollection<Position> _positions;
public ObservableCollection<Position> Positions
{
get { return _positions; }
set
{
_positions = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
People = new ObservableCollection<Person>();
People.Add(new Person { Name = "Name 1", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
People.Add(new Person { Name = "Name 2", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
People.Add(new Person { Name = "Name 3", Position = new Position { PositionId = 2, PositionTitle = "Position Title 2" } });
Positions = new ObservableCollection<Position>();
Positions.Add(new Position { PositionId = 1, PositionTitle = "Position Title 1" });
Positions.Add(new Position { PositionId = 2, PositionTitle = "Position Title 2" });
Command = new RelayCommand(param => EditData());
}
public ICommand Command { get; }
private void EditData()
{
var newData = People;
}
#region Prop Changed
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
View
<DataTemplate DataType="{x:Type local:MainViewModel}">
<StackPanel>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Position Title">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Positions,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
DisplayMemberPath="PositionTitle"
SelectedValue="{Binding Path=Position}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Save new data" Command="{Binding Command}" />
</StackPanel>
</DataTemplate>
Set the UpdateSourceTrigger of the SelectedValue binding to PropertyChanged:
SelectedValue="{Binding Path=Position, UpdateSourceTrigger=PropertyChanged}"
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 a Grouped ListView bound to MyGroup (see below) that contains some property but when that property is being changed the view is not getting updated although I call OnPropertyChanged
My Class
public class MyGroup : ObservableCollection<Items>, INotifyPropertyChanged
{
private string foo;
public string Foo
{
get => foo;
set
{
foo = value;
OnPropertyChanged(nameof(Foo));
}
}
...
}
My View
<ListView ItemSource="{Binding GroupList}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="#2196F3"
Padding="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label FontSize="Medium" Text="{Binding Header}" Grid.Column="1" FontAttributes="Italic" TextColor="White"/>
<Label Text="{Binding Foo}" TextColor="White" FontSize="Medium" Grid.Column="1" HorizontalOptions="End"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
MyViewModel
public List<MyGroup> GroupList { get => groupList ;set => SetProperty(ref groupList, value); }
According to your description, you bind Foo in ListView Group header, you said that group header don't update when property changed, but I don't see where do you change this property. I've written a sample - I changed the first item group header when button click.
public class PersonList1 : ObservableCollection<Person1>, INotifyPropertyChanged
{
private string _heading;
public string Heading
{
get { return _heading; }
set
{
_heading = value;
RaisePropertyChanged("Heading");
}
}
public ObservableCollection<Person1> Persons => this;
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Person1
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string DisplayName
{
get
{
return $"{LastName}, {FirstName}";
}
}
}
<StackLayout>
<ListView IsGroupingEnabled="true" ItemsSource="{Binding ListOfPeople}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Heading}" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding DisplayName}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button
x:Name="btn1"
Clicked="Btn1_Clicked"
Text="change header" />
</StackLayout>
public partial class Page15 : ContentPage
{
public ObservableCollection<PersonList1> ListOfPeople { get; set; }
public Page15 ()
{
InitializeComponent ();
var sList = new PersonList1()
{
new Person1() { FirstName = "Sally", LastName = "Sampson" },
new Person1() { FirstName = "Taylor", LastName = "Swift" },
new Person1() { FirstName = "John", LastName = "Smith" }
};
sList.Heading = "S";
var dList = new PersonList1()
{
new Person1() { FirstName = "Jane", LastName = "Doe" }
};
dList.Heading = "D";
var jList = new PersonList1()
{
new Person1() { FirstName = "Billy", LastName = "Joel" }
};
jList.Heading = "J";
ListOfPeople = new ObservableCollection<PersonList1>()
{
sList,
dList,
jList
};
this.BindingContext = this;
}
private void Btn1_Clicked(object sender, EventArgs e)
{
ListOfPeople[0].Heading = "this is test";
}
}
You can see the the first ListView group header property change as "this is test".
Your question is not complete enough. Please add detail of how you are calling/setting values to property.
BTW, instead of using this(a wild guess,as you have not provided enough info):
public List<MyGroup> GroupList { get => groupList ;set => SetProperty(ref groupList, value); }
try this:
public List<MyGroup> GroupList {
get{
return groupList;
}
set{
groupList = value;
OnPropertyChanged(nameof(GroupList));
}
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);
});