ComboBox SelectedItem not getting set when using custom ItemTemplate - c#

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);
});

Related

Get updated value for a specific DataGridTemplateColumn in WPF

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}"

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"));
}

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

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));
}
}

Wpf two way binding in listbox

trying to do two way binding in the listbox unfortunately does not work.
Here's the xaml code:
<ListBox Margin="19,0,21,149" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here's the viewmodel and person code:
public class ViewModel
{
public ObservableCollection<Person> Items
{
get
{
return new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
}
}
}
public class Person : INotifyPropertyChanged
{
public string _name;
public int Age { get; set; }
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and MainWindow:
public MainWindow()
{
InitializeComponent();
model = new ViewModel();
this.DataContext = model;
}
I do not know what is wrong, trying to bind the property "Name", but it does not work. Please, let me know what may be wrong.
It was enough to change:
public class ViewModel
{
ObservableCollection<Person> _items = new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
public ObservableCollection<Person> Items
{
get
{
return _items;
}
}
}

How to bind Selected Items in MVVM

I am using WPF, MVVM and DevExpress GridControl. There are two panels in my MainWindow.xaml. Panle1 has Grid and Panel2 has Textbox. I want that if i select an item from Grid in Panel1 it's name should display in that Panle2 Textbox. Iwrote Code but it is not working. Can you Please help me to solve this?
*In NameModel From Models Folder I wrote:*
private NameModelClass _selectedCustomer;
public NameModelClass SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
LastName = value.LastName;
OnPropertyChanged("SelectedCustomer");
}
}
public List<Namess> ListPerson { get; set; }
void CreateList()
{
ListPerson = new List<Namess>();
for (int i = 0; i < 10; i++)
{
ListPerson.Add(new Namess(i));
}
}
public class Namess
{
public Namess(int i)
{
FirstName = "FirstName" + i;
LastName = "LastName" + i;
Age = i * 10;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
}
In MianWindow.xaml I wrote:
<dxdo:LayoutPanel Caption="Grid" Caption="Panel1" x:Name="abc1">
<Grid>
<dxg:GridControl x:Name="grid" Height="233" ItemsSource="{Binding ListPerson}" AutoGenerateColumns="AddNew" HorizontalAlignment="Left" VerticalAlignment="Top" SelectedItem="{Binding SelectedNames}">
<dxg:GridControl.View>
<dxg:TableView ShowTotalSummary="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</Grid>
</dxdo:LayoutPanel>
<dxdo:LayoutPanel Caption="Panel2" x:Name="abc1">
<TextBox Width="166" Background="White" Height="33" HorizontalAlignment="Right" VerticalAlignment="Bottom" Text="{Binding Path=LastName}"/>
</dxdo:LayoutPanel>
I am new to MVVM and c#. I f my query is not clear to you please ask me. Thank you.
I do it this way:
private Namess _selectedCustomer;
public Namess SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
OnPropertyChanged("SelectedCustomer");
}
}
public List<Namess> ListPerson { get; set; }
void CreateList()
{
ListPerson = new List<Namess>();
for (int i = 0; i < 10; i++)
{
ListPerson.Add(new Namess(i));
}
}
public class Namess : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public Namess(int i)
{
FirstName = "FirstName" + i;
LastName = "LastName" + i;
Age = i * 10;
}
public string FirstName { get; set; }
private string _lastName;
public string LastName
{
get
{
return _lastName;
}
set
{
if(value==_lastName)
return;
_lastName=value;
OnPropertyChanged("LastName");
}
}
public int Age { get; set; }
}
}
and in your view:
<dxdo:LayoutPanel Caption="Grid" Caption="Panel1" x:Name="abc1">
<Grid>
<dxg:GridControl x:Name="grid" Height="233" ItemsSource="{Binding ListPerson}" AutoGenerateColumns="AddNew" HorizontalAlignment="Left" VerticalAlignment="Top" SelectedItem="{Binding SelectedNames,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<dxg:GridControl.View>
<dxg:TableView ShowTotalSummary="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</Grid>
</dxdo:LayoutPanel>
<dxdo:LayoutPanel Caption="Panel2" x:Name="abc1">
<TextBox Width="166" Background="White" Height="33" HorizontalAlignment="Right" VerticalAlignment="Bottom" Text="{Binding Path=SelectedCustomer.LastName,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
</dxdo:LayoutPanel>
Bsically I changed the type of SelectedCustomer to one of the collection of items. In the view you can set the binding of your TextBox directly to a property of the SelectedCustomer.
It looks like you forgot to raise the INPC (INotifyPropertyChanged) event for the "LastName" string.
So try this (changed is in the setter below):
public NameModelClass SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
LastName = value.LastName;
OnPropertyChanged("SelectedCustomer");
OnPropertyChanged("LastName"); //<-- new
}
}
}
You have to send out INPCs so that the binding knows to update to the new value. The displayed binding won't "grab" the new value for LastName unles you raise that event.
Have you tried:
SelectedItem="{Binding SelectedNames, Mode=TwoWay}"
After looking at it more, your main Namess Class could do with implementing INotifyPropertyChanged
With each property raising the property changed event when it ahem changes.
Also using an observable collection so when you add and remove items it also raises changes.
That way, the notification change system receives the notify of property changes to change the view accordingly via bindings.

Categories