I need to bind a ComboBox's Selected Item according to a value of a field in the data context which it's parent container receives .
the container is a grid which receives it's datacontext from an item in an itemcontrol when it's clicked
private void Button_Click(object sender , RoutedEventArgs e)
{
GridEmployee.DataContext = ((Button)sender).DataContext;
}
*the Button got it's itemsource from a list of Employee's Bounded to the itemControl
the grid holds some controls amongst them a combobox which i initalize through an Enum
public Enum Gender
{
Male,Female
};
foreach(string _gender in Enum.GetNames(Gender) )
{
GenderComboBox.Items.Add(_gender);
}
the Employee class has a matching Property Gender
private string gender;
public string Gender
{
get{return gender;}
set
{
gender = value ;
if( PropertyChanged != null )
PropertyChanged(this,new PropertyChangedEventArgs("Gender"));
}
}
the GenderComboBox.SelectedItem is bounded to the value of the Gender Property for the bounded object Employee
<ComboBox x:Name="GenderComboBox" SelectedItem="{Binding Gender , Mode=TwoWay}" />
the problem here of course that the item does not get selected ..
I tought may be its becuase the items in the combobox are strings and I try to bound them according to a custom converter which just take the Enum Value and returns the .ToString()
of it .
but I was not able to check this becuase that threw an An XamlParseException in form's contractor .
which I did not fully understand why it happend ,may be becuase it did not have a value to convert when I form loads.
so to conclude how do I bind a Property from My Employee Class
to a combobox with the string representation of the Property's Value?
Works nicely in my case....
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="GenderSelection" Height="100" Width="300" x:Name="MyWindow">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock FontSize="40"
Text="{Binding MyGender, ElementName=MyWindow, Mode=TwoWay}"/>
<ComboBox Grid.Row="1"
ItemsSource="{Binding Genders, ElementName=MyWindow}"
SelectedItem="{Binding MyGender, ElementName=MyWindow, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Code Behind
public enum Gender
{
Male,
Female
}
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window, INotifyPropertyChanged
{
private string myGender = Gender.Male.ToString();
public string MyGender
{
get
{
return myGender;
}
set
{
myGender = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MyGender"));
}
}
}
public string[] Genders
{
get
{
return Enum.GetNames(typeof(Gender));
}
}
public Window1()
{
InitializeComponent();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Let me know if this guides you in correct direction...
Just change the initialization of your ComboBox to
foreach(string _gender in Enum.GetNames(Gender) )
{
GenderComboBox.Items.Add(_gender.ToString());
}
That should work, because your Gender property of the Employees class returns a string.
Related
I want to bind a textbox to a selected DataGrid. I have already binded the list to a datagrid but now I would like to bind the TextBoxtext to the DataGridselected row so its content will be placed into the TextBox
txtOccArea.DataContext = hegData;
//hegData is a list of an object
Thanks!
You should create a new class like this ( I hope you are using MVVM ).
public class YourViewVM : INotifyPropertyChanged
{
#region Fields
private object selectedDataGridCell;
private string textBoxContent;
private List<YourObject> dataGridSource;
#endregion
#region Properties
public object SelectedDataGridCell
{
get
{
return this.selectedDataGridCell;
}
set
{
if (this.selectedDataGridCell != value)
{
this.selectedDataGridCell = value;
OnPropertyChanged("SelectedDataGridCell");
}
}
}
public string TextBoxContent
{
get
{
return this.textBoxContent;
}
set
{
if (this.textBoxContent != value)
{
this.textBoxContent = value;
OnPropertyChanged("TextBoxContent");
}
}
}
public List<YourObject> DataGridSource
{
get
{
return this.dataGridSource;
}
set
{
if (this.dataGridSource != value)
{
this.dataGridSource = value;
OnPropertyChanged("Source");
}
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In your view, just modify it to:
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding DataGridSource}" SelectedItem="{Binding SelectedDataGridCell}" />
<TextBox Grid.Row="1" Text="{Binding TextBoxContent}"></TextBox>
</Grid>
You need to add the INotifyPropertyChanged so the TextBox knows when the selection has changed.
If you need to set the DataGridSource to your hegData list, just create a constructor and set the property there like this:
public YourViewVM(List<YourObject> hegData)
{
this.DataGridSource = hegData;
}
And where you create it just call it like:
YourViewVM yourViewVM = new YourViewVM(hegData)
If you just want to display the value in the TextBox it will be ok to bind in XAML. Try this:
<DataGrid x:Name="MyGrid" ItemsSource="{Binding hegData}"/>
<TextBox Text={Binding SelectedItem, ElementName=MyGrid}/>
If you actually need to alter the Selected Item, I think you should define a SelectedListItem property in your ViewModel and bind the TextBox's text to this property.
ViewModel:
public List<object> hegData {get;set;}
public object SelectedListItem {get;set;}
View:
<DataGrid ItemsSource="{Binding hegData}"
SelectedItem="{Binding SelectedListItem}"/>
<TextBox Text={Binding SelectedListItem}/>
I have UserControl with ItemsControl binded to ObservableCollection. DataTemplate in this ItemsControl is a Grid containing TextBox and Button.
Here is some code (Updated):
<UserControl.Resources>
<entities:SeparatingCard x:Key="IdDataSource"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Cards}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" GotFocus="TextBox_GotFocus" Grid.Row="0" Grid.Column="0"/>
<Button DataContext="{Binding Source={StaticResource IdDataSource}}" Command="{Binding Accept}" Grid.Row="0" Grid.Column="1">Accept</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In model file:
public ObservableCollection<SeparatingCard> Cards { get; set; }
Card class:
class SeparatingCard : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public ActionCommand Accept { get; }
public SeparatingCard()
{
Accept = new ActionCommand(AcceptCommandExecute);
}
private void AcceptCommandExecute(object obj)
{
MessageBox.Show(Id);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Cards are added in runtime and I dynamically get a new textbox-button pair in my UserControl. Now in each pair I need to do the folowing things:
- Be able to check if the text in textbox is correct and disable/enable apropriate button.
- On button click get the text from apropriate textbox and process it.
I'd like all of this done via MVVM. But I only came to solution that directly have access to UI and implements only the second task:
private void Button_Click(object sender, RoutedEventArgs e)
{
var text = (((sender as Button).Parent as Grid).Children
.Cast<UIElement>()
.First(x => Grid.GetRow(x) == 0 && Grid.GetColumn(x) == 0) as TextBox).Text;
MessageBox.Show(text);
}
Update
As was suggested I tried to move ICommand logic to SeparatingCard class. Now it's always return null and I can't check what object of SeparatingCard class my command refers to. Updates are in the code above.
Instead of using Button.Click, Use Button.Command, which you can bind to some command in SeparatingCard.
Please have a look in this tutorial:
http://www.codeproject.com/Tips/813345/Basic-MVVM-and-ICommand-Usage-Example
Then, SeparatingCard ViewModel will contain an ICommand object which you can bind to Button.Command.
So if the user clicks the button, the event will be directed to the corresponding SeparatingCard object's command.
I've got a dilemma here. So I have mutiple expanders stacked on top of one another. Inside each expander is a ListBox, data bound, where each listitem displays a name of an object.
I've bound the search to filter the list items based on their name. However since I have two observable objects, the filtered items and unfiltered, the UI doesn't appear to get populated until someone searches. Whats the best way to fix this. I find it redundant to add items to both lists each time a new Person gets created. Using an mvvm approach.
The two collections are called People and PeopleFiltered. When i create people I add them to the list called People. When the search is applied it populates the PeopleFiltered list, which is the list the UI is bound to. How can I maintain this list be init to mimic People.
At the end of the day the PeopleFiltered collection should mimic People unless a search is being applied.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="400" Width="200">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Search:"/>
<TextBox Grid.Column="1" Background="Gold" Text="{Binding SearchString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
<StackPanel Grid.Row="1">
<Expander Header="People" IsExpanded="{Binding IsExpanded, Mode=OneWay}">
<ListView ItemsSource="{Binding PeopleFiltered}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Ellipse Width="8" Height="8" Fill="Green" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Name}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</StackPanel>
</Grid>
</Window>
MainWindowViewModel.cs
using System.Collections.ObjectModel;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Collections.Generic;
namespace WpfApplication1
{
public class MainWindowViewModel : NotifyBase
{
// search text
private string searchString;
public string SearchString
{
get { return searchString; }
set
{
this.searchString = value;
NotifyPropertyChanged("SearchString");
ApplySearchFilter();
}
}
private void ApplySearchFilter()
{
if (string.IsNullOrWhiteSpace(SearchString))
{
IsExpanded = false;
PeopleFiltered.Clear();
foreach (DisplayItem displayItem in People)
{
PeopleFiltered.Add(displayItem);
}
}
else
{
// open expanders and apply search
IsExpanded = true;
PeopleFiltered.Clear();
foreach (DisplayItem displayItem in People)
{
if (displayItem.Name.ToLowerInvariant().Contains(SearchString.ToLowerInvariant()))
{
PeopleFiltered.Add(displayItem);
}
}
}
}
// used to to open and close expanders
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
// data collections for each expander
private ObservableCollection<DisplayItem> people;
public ObservableCollection<DisplayItem> People
{
get { return people ?? (people = new ObservableCollection<DisplayItem>()); }
set
{
people = value;
NotifyPropertyChanged("People");
}
}
private ObservableCollection<DisplayItem> peopleFiltered;
public ObservableCollection<DisplayItem> PeopleFiltered
{
get { return peopleFiltered ?? (peopleFiltered = new ObservableCollection<DisplayItem>()); }
set
{
peopleFiltered = value;
NotifyPropertyChanged("PeopleFiltered");
}
}
// init
public MainWindowViewModel()
{
// People
People.Add(new DisplayItem() { Name="John" });
People.Add(new DisplayItem() { Name="Veta"});
People.Add(new DisplayItem() { Name="Sammy"});
People.Add(new DisplayItem() { Name = "Sarah" });
People.Add(new DisplayItem() { Name = "Leslie" });
People.Add(new DisplayItem() { Name = "Mike" });
People.Add(new DisplayItem() { Name = "Sherry" });
People.Add(new DisplayItem() { Name = "Brittany" });
People.Add(new DisplayItem() { Name = "Kevin" });
}
}
// class used to display all items
public class DisplayItem
{
public string Name { get; set; }
}
//observable object class
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here's a nice approach.
Create a Query property in your View Model, this will be bound to your filter TextBox.
private string _Query;
public string Query
{
get { return _Query; }
set
{
_Query = value;
Filter();
//Notify property changed.
}
}
One thing to note here is the Filter() method. This will be called every time the property changes, I'll get back to this later. Firstly, make sure your TextBox binding is TwoWay, it'll look like this:
<TextBox Text="{Binding Query}" ... />
In your View Model, you will need a collection for each ListBox.
private List<object> _Collection1; //The original collection
private List<object> _FilteredCollection1; //The filtered collection
public List<object> FilteredCollection1
{
get { return _FilteredCollection1; }
set
{
_FilteredCollection1 = value;
//Notify property changed.
}
}
//Some more collections
...
It's important to note here that there is a variable for the original unfiltered collection. This is important because we want to filter this list into a new collection, otherwise we'll just keep filtering over and over and eventually have nothing in the collection.
You'll need to bind the ItemsSource property in your ListBox to the collection(s).
<ListBox ItemsSource="{Binding FilteredCollection1}" ... />
Now, your Filter method can simply filter the _Collection1 variable into the FilteredCollection1 property.
private void Filter()
{
//Perform the filter here for all collections.
FilteredCollection1 = _Collection1.Where(x => x.Something == Query);
//Do the same for all other collections...
}
Note: The above Linq is just an example, I expect yours will be slightly more complicated than that, but you get the idea.
So. Whenever Query gets updated, the Filter collection will fire, update the FilteredCollection properties which will in turn call property changed and update the view.
Alternative Approach
Here's another way.
Instead of using a Filter method, you can instead put your filter code inside the get block in the FilteredCollection properties, like this:
public List<object> FilteredCollection1
{
get
{
return _Collection1.Where(...);
}
}
Then, in your Query property, simply call INotifyPropertyChanged for the collection:
private string _Query;
public string Query
{
get { return _Query; }
set
{
_Query = value;
//Notify property changed.
OnPropertyChanged("FilteredCollection1");
}
}
This will force the view to refresh the FilteredCollection1 property.
I'm trying to get the databinding I need to work with a ListBox.
I've parsed some data from a text file to a ObservableCollection<ViewModel> but the data isn't updating in the ListBox.
Here's some information:
The data which is written to from the parser:
class MainData
{
private static ObservableCollection<GroupViewModel> groupModelList = new ObservableCollection<GroupViewModel>();
public static ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
What GroupViewModel holds (not everything but it's all the same):
class GroupViewModel : INotifyPropertyChanged
{
private GroupModel groupModel;
public event PropertyChangedEventHandler PropertyChanged;
public GroupViewModel()
{
groupModel = new GroupModel();
}
public string Name
{
get { return groupModel.name; }
set
{
if (groupModel.name != value)
{
groupModel.name = value;
InvokePropertyChanged("Name");
}
}
}
...
}
And what GroupModel Holds:
class GroupModel
{
public string name { get; set; }
}
This is how the parser adds new items to the GroupModelView:
if (split[0] == "group")
{
currentGroup = new GroupViewModel();
currentGroup.Name = split[1];
MainData.GroupModelList.Add(currentGroup);
}
I created a ListBox in my WPF application with these XAML options:
<Window x:Class="SoundManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SoundManager.ViewModels"
xmlns:vm2="clr-namespace:SoundManager.Code"
Title="MainWindow" Height="720" Width="1280">
<Window.Resources>
<vm:MainViewModel x:Key="MainViewModel" />
<vm2:MainData x:Key="MainData" />
</Window.Resources>
<ListBox Grid.Row="2" Height="484" HorizontalAlignment="Left" Margin="12,0,0,0" Name="lbFoundItems" VerticalAlignment="Top" Width="201" ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList/Name}" />
but for some reason the data isn't updating in the UI (new items aren't added visibly in the UI).
I've been just getting started with the MVVM pattern and databinding and I can't figure out what I'm doing wrong.
Thanks in advance!
GroupModelList/Name is not a valid property path here. Setting it like that does not make the ListBox show the Name property of the data items in the GroupModelList collection.
You would instead have to set the ListBox's DisplayMemberPath property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}"
DisplayMemberPath="Name"/>
or set the ItemTemplate property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Moreover, the GroupModelList property should not be static:
class MainData
{
private ObservableCollection<GroupViewModel> groupModelList =
new ObservableCollection<GroupViewModel>();
public ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
Then you might have MainData as a property in your view model, and bind the ListBox like this:
<ListBox ItemsSource="{Binding Source={StaticResource MainViewModel},
Path=MainData.GroupModelList}" .../>
i have a view that have a list view with data template
i need to set style on the selected item
but i need also when the selected item is been changed from the code it modify the selected item in the view
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" Width="300" Height="50" TextAlignment="Center"/>
<ListView Grid.Row="1" ItemsSource="{Binding List, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate >
<Border BorderThickness="1" BorderBrush="White">
<Grid Height="20" Width="30" >
<TextBlock Text="{Binding Name}"/>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
there is a list view and textblock
i need when the selectedItem changed it changed the the background of the selected item
here is the viewmodel
public class MainViewModel : ViewModelBase
{
private Item selectedItem;
public ObservableCollection<Item> List { get; set; }
string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged("Text");
}
}
public Item SelectedItem
{
get { return selectedItem; }
set{
if (value.Name != "Test1")
{
selectedItem = value;
Text = value.Name;
}
else
{
Text = string.Format("Test1 was selected but the selected item is {0}", selectedItem==null?"null":selectedItem.Name);
}
OnPropertyChanged("SelectedItem");
}
}
public MainViewModel()
{
List = new ObservableCollection<Item>()
{
new Item("Test1","Val1"),new Item("Test2","Val2"),new Item("Test3","Val3"),new Item("Test4","Val"),
};
OnPropertyChanged("List");
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (var propertyName in propertyNames)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
}
public class Item : ViewModelBase
{
public string Name { get; set; }
public string Value { get; set; }
public Item(string name, string val)
{
Name = name;
Value = val;
OnPropertyChanged("Name");
}
}
note that when the Test1 Item selected the selected item didnot changed but in the view Test1 is marked as selected
At the point your MainViewModel.SelectedItem setter is called by the view, the view has already updated its selected item in the list. The binding simply informs the VM of this fact. The fact that you don't set MainViewModel.selectedItem means nothing to the view.
You would think that raising OnPropertyChanged("SelectedItem"); would force the view to re-evaluate its selected item, but in practice this does not work. I assume is down to some optimization within WPF or to prevent cyclic binding updates. (Remember you setter is already being called as part of a binding update, and you are trying to update the binding again)
If you wish to prevent something being selected in the view, then you need to disable it within the view, before it gets down to the VM. Here is one way of doing this.