Get selected items from a list box with check boxes - c#

A have a list box with check boxes (I removed the part about alignment, width, margin as not related to the case):
<ListBox
ItemsSource ="{Binding SItemCollection}"
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Item.Code}" IsChecked="{Binding IsChecked}"/>
</DataTemplate>
</ListBox.ItemTemplate>
I have a class SItem inside my ViewModel, which stores two fields - CachedStr which I get from Cache and a Boolean IsChecked which represents whether the item is checked or not (CachedStr object also has several fields (Name, Code etc), I've chosen to show the Code):
public class SItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CachedStr Item { get; set; }
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
NotifyPropertyChanged("IsChecked");
}
}
protected void NotifyPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
A have a Collection of SItems (SItemCollection), which fills my ListBox with items, some of which are ticked. This collection is outside the SItem class, it is inside my view model. I also have a set of all items (AvailableSItems) that should be available in the ListBox and a set of items that should be checked (ItemsToBeTicked) at the very beginning. This two sets contain objects of type CachedStr. By using those sets I get my SItemCollection:
public ObservableCollectionEx<SItem> SItemCollection
{
get
{
ObservableCollectionEx<SItem> strItems = new ObservableCollectionEx<SItem>();
this.AvailableSItems.ForEach(p =>
{
SItem item = new SItem();
item.Item = p;
item.IsChecked = false;
strItems.Add(item);
});
strItems.ForEach(p =>
{
if (this.ItemsToBeTicked.Contains(p.Item))
{
p.IsChecked = true;
}
else p.IsChecked = false;
}
);
return strItems;
}
}
The above-mentioned code works. But I also need a method which will get the final set of all ticked items (after, for example, pressing the button), and that's where I'm stuck. I do get a notifications when I tick or untick something.

The code currently creates a new instance of the collection in the get block. This has to be changed, otherwise the changes done in the UI will be reverted each time the get block is called.
Take the code which is currently in the get block, extract it to a method and use the return value of the method to set your SItemCollection property.
In constructor for example:
SItemCollection = CreateInitialCollection();
And the property will be simplyfied to:
public ObservableCollectionEx<SItem> SItemCollection
{
get
{
return _sitemCollection;
}
set
{
if (_sitemCollection!= value)
{
_sitemCollection= value;
RaisePropertyChanged("SItemCollection");
}
}
}
ObservableCollectionEx<SItem> _sitemCollection;
When this is fixed (and if the binding to the IsChecked property in SItem works), you can use a Linq expression:
var checkedItems = SItemCollection.Where(item => item.IsChecked == true)

Related

How to Manipulate Output based on ListView Item Source Property

I am setting ListView ItemSource to a List<T> where T is my Model. I am Binding some of the Property of this List<T> to some Label in XAML. And Now based on a Property, I want to Set Label to some Text.
For Example, if (Property.IsCompleted == true), I might want to set a Label in my View Cell in the ListView to "Done" instead of "True".
I hope this summarizes the problem. I have tried other things and none worked.
This is the Item Appearing Method of My ListView:
private void bookingLV_ItemAppearing(object sender, ItemVisibilityEventArgs e)
{
BookingsModel convert = (BookingsModel)e.Item;
var select = convert.IsCompleted;
if(select == true)
{
IsDone = "Completed";
}
IsDone = "Pending";
}
And I have a Custom Property called IsDone:
public string IsDone { get; set; }
And This is how I am Binding IsDone in the View Cell of the ListView in Xaml
<Label Text="{Binding IsDone}"></Label>
I want to be able to set the Text Property of my Label to some text based on a property of my Model Object.
create a read only property in your model that returns a value based on another property
public string IsDone
{
get
{
if (select) return "Completed";
return "Pending";
}
}
if you are using INotifyPropertyChanged you will want to be sure that the setter of the "trigger" property fires PropertyChanged events for both
public bool selected {
get {
...
}
set {
...
PropertyChanged("selected");
PropertyChanged("IsDone");
}
}

WPF: Databinding to a DataGrid

This is my first post in this forum, though I am a long-time lurker. I have started learning WPF for about a couple of months now, and I am trying to create an application just for training purposes.
I have a backend database which I have added to my application using EF6 ORM. In my application, I have a `ComboBox which needs to be populated by a column in a table of the database. That I can do using binding to a list.
The part I am having trouble with is the DataGrid. The columns of the DataGrid needs to be populated according to the Item chosen in the ComboBox.
My database:
As you can see, the school has several departments, and each of those department has a HOD and a student strength.
My application:
The ComboBox will be populated with school names. The DataGrid will be populated once the schoolname is selected. The DataGrid will have each row for each department available for the school. So I need to bind the corresponding columns with the departments of the corresponding schools. That much I get. However, then I want to save the user-entered comments in the Feedback TextBox.
I cannot understand how to create a class so that I can bind the DataGrid to the object of it. Is it possible to bind the DataGrid to an object and then bind the columns separately to another object?
EDIT
Apart from the entities created from the database, I have two classes:
class Feedback : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _school;
public string School
{
get
{
return _school;
}
set
{
_school = value;
OnPropertyChanged("School");
}
}
private ObservableCollection<FeedbackLine> _feedbackLines;
public ObservableCollection<FeedbackLine> FeedbackLines
{
get
{
return _feedbackLines;
}
set
{
_feedbackLines = value;
OnPropertyChanged("FeedbackLines");
}
}
public Feedback(string school)
{
//Insert some Linq Query to populate the FeedbackLines
//something like
//var FeedbackLines = Context.Schools.Where(c => c.SchoolName == school)
// .Select(c => new {Department = c.AvailableDepts.Dept, etc etc}.ToList();
//but then what?
}
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
}
This is supposed to be bound to the datagrid. And the FeedbackLine is:
public class FeedbackLine: INotifyPropertyChanged
{
private string _dept;
public string Department
{
get { return _dept; }
set { _dept = value;
OnPropertyChanged("Department");
}
}
private string _HOD;
public string HOD
{
get { return _HOD; }
set { _HOD = value;
OnPropertyChanged("HOD");
}
}
private int _strength;
public int Strength
{
get { return _strength; }
set { _strength = value;
OnPropertyChanged("Strength");
}
}
private bool _isSelected;
public bool Selected
{
get { return _isSelected; }
set { _isSelected = value;
OnPropertyChanged("Selected");
}
}
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value;
OnPropertyChanged("Comment");
}
}
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I haven't had much headway with the ViewModel. Problem is, I am not very good with LINQ. And there are too many classes and objects and I have no idea which one to bind with which. The only vague idea that I can get is that I have to use LINQ to query the database using the selected School, and then populate the FeedbackLines using that.
Edit 2:
For anyone who's interested, here's my model diagram in WPF:
Model Diagram
Edit 3:
I think I am confused about ViewModel. The data that will be displayed on the screen is not necessarily the data to be saved. For example, I don't want to save the unselected rows. My Feedback class tries to display the data as well as save it. And therein lies the problem. Can't a DataGrid be bound to an object, while its columns be bound to other objects? For example, if I choose to use a Combobox for Department. Then I need to use ItemsSource for displaying items, but need to save the SelectedItem only. I can't find a way to separate these two concerns.
I would change your Feedback constructor
public Feedback(string school, List<FeedbackLine> feedbackLines)
{
School = school;
FeedbackLines = new ObservableColleytion<FeedbackLine>(feedbackLines);
}
It's a better architecture if your data viewmodel does not have a connection to the database. You can put your select in a seperate class.
If you need help with your LINQ statement I can help you.
In your Feedback constructor you wrote
//but then what?
When you got your data you can create instances of FeedbackLines to add them in the new constructor I showed above.
When you did this your viewmodel (which is DataContext of your view) needs an
public void ObservableCollection<Feedback> Feedbacks
with INotifyPropertyChanged like you did it in the other viewmodels.
In your xaml you have your ComboBox with the schools. Give that combobox a name, e.g. SchoolsComboBox.
In your DataGrid write this line
Source={Binding ElementName=SchoolsComboBox, Path=SelectedItem.FeedbackLines}
/edit for adding LINQ
You created an anonymous type. Just create a FeedbackLine instead and you're fine.
var feedbackLines = Context.Schools.Where(c => c.SchoolName == school)
.Select(c => new FeedbackLine
{
Department = c.AvailableDepts.Dept,
HOD = c.AvailableDepts.HeadOfDept,
Strength = c.AvailableDepts.StudentStrength}
.ToList()
U can make Something like this. Im sure it can be writen better but it works.
In your ViewModel make 3 Properties that implements INotifyPropertyChanged
One for your collection that will you bind to your ComboBox(make it ObservableCollection), One for SelectedItem from your ComboBox( you bind it to SelectedItem in comboBox) and another ObservableCollection that you will Bind to DataGrid)
For example you have in XAML:
<Grid>
<ComboBox ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="200"
Margin="20"
IsSynchronizedWithCurrentItem="True" />
<DataGrid ItemsSource="{Binding SelectedOne}"
HorizontalAlignment="Right "
VerticalAlignment="Center"
Width="300"
IsSynchronizedWithCurrentItem="True">
</DataGrid>
and in your ViewModel you can have something like this.
public ObservableCollection<Product> Products
{
get { return _products; }
set
{
if (value != _products)
{
_products = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<Product> _selectedOne;
public ObservableCollection<Product> SelectedOne
{
get { return _selectedOne; }
set {
_selectedOne = value;
OnPropertyChanged();
}
}
public int SelectedProductId
{
get { return _selectedProductId; }
set
{
if (value != _selectedProductId)
{
_selectedProductId = value;
OnPropertyChanged();
}
}
}
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
if (value ! = _selectedProduct)
{
_selectedProduct = value;
// clear your list of selected objects and then add just selected one
// or you dont clear it, and items will be added in DataGrid when selected in ComboBox
SelectedOne.Clear();
SelectedOne.Add(_selectedProduct);
OnPropertyChanged();
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Code to Populate Products, DataGrid will be populated by selecting Item from ComboBox.
u can go in ViewModel constructor and make something like this.
public MainWindowViewModel()
{
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
using (YourDbContext context = new YourDbContext ())
{
var productList = new ObservableCollection<Product>(context.Products);
productList.ToList()
Products = productsList;
}
}
}

How can I properly reset a value associated with a ComboBox, from within a PropertyChanged event?

I have a ComboBox that is bound to a property on my ViewModel (from hear on "VM".) When a user makes a selection on the ComboBox it properly updates the bound property in my VM. Within my UI code, I have subscribed to the PropertyChanged event on my VM.
As it should behave, when the user makes a selection within the ComboBox, my PropertyChanged event is correctly executing in my UI back-end code. When the UI code catches the change of this property, under certain selection conditions I need to halt the process and request the user for additional information. From the UI, I send them a dialog. If they cancel the dialog, I reset the value in the VM that is associated with the ComboBox controls SelectedValue.
This is what I've observed. When the operation is cancelled by the user, my VM property is being set to the new value. However, the ComboBox is still showing the text value of the original entry that has now changed. How can I force the ComboBox to update itself from within my PropertyChanged event? In this case, I think it's just a textual issue or numeric index change that's referencing the text data from the bound collection. The data is correct in the VM but the display value for the ComboBox is wrong.
EXAMPLE
ComboBox Details
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
Height="27" />
Sorry for the wordy properties on the VM, but that's to explain what's happening. My ListOfComboBoxDisplayObjects collection represents a set of enumerator values that are stored in the path within SelectedValuePath. The descriptive text for each value is pulled from the ListOfComboBoxDisplayObjects which is a special list strictly created for the UI. This basically pairs an enumeration value with a meaningful description.
ListOfComboBoxDisplayObjects Definition (from within VM)
Edit #1 - Added this definition to my example
private ObservableCollection<BindableEnumerationItem<Values>> _listOfComboBoxDisplayObjects;
public ObservableCollection<BindableEnumerationItem<Values>> ListOfComboBoxDisplayObjects
{
get { return _listOfComboBoxDisplayObjects; }
private set
{
if (value != _listOfComboBoxDisplayObjects)
{
_listOfComboBoxDisplayObjects= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ListOfComboBoxDisplayObjects)));
}
}
}
MySelectionIsAnEnumeration Definition (From within VM)
*Edit #1: Adding this code definition.
private Values_mySelectionIsAnEnumeration ;
public Values MySelectionIsAnEnumeration
{
get { return _mySelectionIsAnEnumeration; }
set
{
//Double-checked this-- value is different on the second-call to change this value, once the UI cancels the operation.
if (value != _mySelectionIsAnEnumeration)
{
_mySelectionIsAnEnumeration= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MySelectionIsAnEnumeration )));
}
}
}
Pertinent Values Associated with ListOfComboBoxDisplayObjects
These values are generated in the ctor of the VM. They are fixed throughout the application.
Item #1
Text: "This is a Foo!"
Value: Values.Foo
Item #2:
Text: "Hi, I'm Bar."
Value: Values.Bar
Item #3:
Text: "This is Baz. I need to ask a question before I can be used."
Value: Values.Baz
PropertyChanged Event - From the UI Back-End
private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MySelectionIsAnEnumeration":
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if(!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
break;
}
}
After executing the above code, what I'm observing is that the VM.MySelectionIsAnEnumeration value is indeed changing to the proper value of Value.Foo when a user cancels the operation within AskAQuestionAndGetAResult(). However, after it's finished the ComboBox still reads "This is Baz. I need to ask a question before I can be used.", which is obviously the display value associated with Value.Baz.
How can I update both the underlying VM property AND the display text on the CombobBox to correctly show the valued that is now stored in VM.MySelectionIsAnEnumeration?
Edit #2
Below is the code efor my BindableEnumerationItem that I use within my Observable Collections for comboxes and list boxes. This is used throughout my application in simpler cases and has caused no issue. Please note, this is my actual, unaltered code. I've not renamed anything. My comboboxes can bind to each Item property for a type-safe property and DisplayText is the descriptor text.
public class BindableEnumerationItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private T _item;
public BindableEnumerationItem(T item, string displayText)
{
_item = item;
_displayText = displayText;
}
private string _displayText;
public string DisplayText
{
get { return _displayText; }
set
{
if (value != _displayText)
{
_displayText = value;
PropertyChanged(this, new PropertyChangedEventArgs("DisplayText"));
}
}
}
public T Item
{
get { return _item; }
set
{
_item = value;
PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
}
create an extension that will wire up the command from your viewmodel in xaml to the selector, which in this case is the combobox.
public partial class Extensions
{
public static readonly DependencyProperty SelectionChangedCommandProperty = DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Selector;
if (element != null)
{
element.SelectionChanged -= OnSelectionChanged;
if (e.NewValue != null)
{
element.SelectionChanged += OnSelectionChanged;
}
}
}));
public static ICommand GetSelectionChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(UIElement element, ICommand value)
{
element.SetValue(SelectionChangedCommandProperty, value);
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var element = sender as Selector;
var command = element.GetValue(SelectionChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element.SelectedItem))
{
command.Execute(element.SelectedItem);
e.Handled = true;
}
}
}
Create the command in the viewmodel that handles the value changed event.
public ICommand EnumerationValueChangedCommand
{
get
{
return new Command(
() =>
{
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if (!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
});
}
}
And then bind using that extension. ext is the namespace for your extensions.
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
ext:Extensions.SelectionChangedCommand="{Binding EnumerationValueChangedCommand}"
Height="27" />

How to bind ComboBox properly in WPF?

I'm new to WPF and MVVM and I'm developing a test WPF application following the MVVM design pattern. My database has 2 entities, Cards and Departments. Any card can have only 1 department, so it's a one-to-many relationship.
I've created the following ViewModel in order to bind to the view:
public class CardViewModel : INotifyPropertyChanged
{
public CardViewModel(Card card)
{
this.Card = card;
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = ".\\SQLExpress";
builder.InitialCatalog = "TESTDB";
builder.IntegratedSecurity = true;
SybaseDatabaseContext myDB = new SybaseDatabaseContext(builder.ConnectionString);
var query = from d in myDB.Departments
select d;
this.Departments = new ObservableCollection<Department>(query);
}
private Card _Card;
private ObservableCollection<Department> _Departments;
public Card Card
{
get { return _Card; }
set
{
if (value != this._Card)
{
this._Card = value;
SendPropertyChanged("Card");
}
}
}
public ObservableCollection<Department> Departments
{
get { return _Departments; }
set
{
this._Departments = value;
SendPropertyChanged("Departments");
}
}
#region INPC
// Logic for INotify interfaces that nootify WPF when change happens
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanged(String propertyName)
{
if ((this.PropertyChanged != null))
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
The CardForms' datacontext is currently being set to an instance of the CardViewModel in the code where the CardForm is being instantiated, but I'm going to create a IoC container or dependency injections down the line.
Everything binds correctly except for the ComboBox that should contain all departments and that has the current department in the Card instance selected (card.Department). Here's the XAML for the ComboBox:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="350,64,0,0"
Name="comboBoxDepartment" VerticalAlignment="Top" Width="120"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Departments}"
DisplayMemberPath="DepartmentName"
SelectedItem="{Binding Path=Card.Department, Mode=TwoWay}" />
The departments are displayed in the combobox, but the current department of the card isn't and if I try to change it I get and error saying "Cannot add an entity with a key that is already in use".
So, my question is, how do I bind this combobox correctly to my ViewModel?
P.S. I know populating the ObservableCollection<Department> in the ViewModel is probably not the right way to do it, but I could not think of a better way at the time. If you have any suggestions for this also, please let me know.
Additionally, this is the Card model:
[Table(Name = "Card")]
public class Card : INotifyPropertyChanged, INotifyPropertyChanging
{
private string _CardID;
private string _Holder;
private Int16? _DepartmentNo;
[Column(UpdateCheck = UpdateCheck.WhenChanged)]
public string CardID
{
get
{
return this._CardID;
}
set
{
if (value != this._CardID)
{
SendPropertyChanging();
this._CardID = value;
SendPropertyChanged("CardID");
}
}
}
[Column(UpdateCheck = UpdateCheck.WhenChanged)]
public string Holder
{
get
{
return this._Holder;
}
set
{
if (value != this._Holder)
{
SendPropertyChanging();
this._Holder = value;
SendPropertyChanged("Holder");
}
}
}
[Column(CanBeNull = true, UpdateCheck = UpdateCheck.WhenChanged)]
public Int16? DepartmentNo
{
get
{
return this._DepartmentNo;
}
set
{
if (value != this._DepartmentNo)
{
SendPropertyChanging();
this._DepartmentNo = value;
SendPropertyChanged("DepartmentNo");
}
}
}
private EntityRef<Department> department;
[Association(Storage = "department", ThisKey = "DepartmentNo", OtherKey = "DepartmentNo", IsForeignKey = true)]
public Department Department
{
get
{
return this.department.Entity;
}
set
{
Department previousValue = this.department.Entity;
if (((previousValue != value)
|| (this.department.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this.department.Entity = null;
previousValue.Cards.Remove(this);
}
this.department.Entity = value;
if ((value != null))
{
value.Cards.Add(this);
this._DepartmentNo = value.DepartmentNo;
}
else
{
this._DepartmentNo = default(Nullable<short>);
}
this.SendPropertyChanged("Department");
}
}
}
I edited the constructor in the CardViewModel to take the DataContext as a parameter and that did it. This is the new CardViewModel constructor:
public CardViewModel(Card card, SybaseDatabaseContext myDB)
{
this.Card = card;
var query = from d in myDB.Departments
select d;
this.Departments = new ObservableCollection<Department>(query);
}
Had to do a bit of research on this myself. Thought I would contribute with a self answered question, but found this open current question...
The ComboBox is designed to be a kind of textbox that restricts it's possible values to the contents of a given list. The list is provided by the ItemsSource attribute. The current value of the ComboBox is the SelectedValue property. Typically these attributes are bound to relevant properties of a corresponding ViewModel.
The following example shows wired ComboBox together with a TextBox control used to redundantly view the current value of the ComboBox by sharing a view model property. (It is interesting to note that when TextBox changes the shared property to a value outside the scope of the ComboBox's list of values, the ComboBox displays nothing.)
Note: the following WPF/C# example does does use code-behind and so presents the ViewModel as merely the datacontext of the view and not a partial class of it, a current implementation constraint when using WPF with F#.
WPF XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<m:MainWindowVM />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SelectedString}" />
<ComboBox ItemsSource="{Binding MyList}" SelectedValue="{Binding SelectedString}" />
</StackPanel>
</Window>
C# ViewModel
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication1
{
public class MainWindowVM : INotifyPropertyChanged
{
string selectedString;
void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string SelectedString
{
get { return selectedString; }
set
{
selectedString = value;
NotifyPropertyChanged("SelectedString");
}
}
public List<string> MyList
{
get { return new List<string> { "The", "Quick", "Brown", "Fox" }; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
By default, ToString() is used to interpret the objects in the list. However, ComboBox offers DisplayMemberPath and SelectedValuePath attributes for specifying paths to specific object properties for corresponding displayed and stored values. These paths are relative to the list object element so a path of "Name" refers to Name on a list object item.
The "Remarks" section of this MSDN link explains the interpretations of the IsEditable and IsReadOnly ComboBox properties.

How to bind a combo-box to a collection of multi-language values in WPF?

I am trying to set up a multi-language application, so when the user changes the display language all the texts in all the open windows change automatically.
I am having issues through with binding combo-box control. The binding needs to be done in code-behind as I have dynamic content coming from a database, and sometimes I even have to create additional combo-boxes at runtime.
Also I do not want to keep the translations in the database because I do not want to query the database every time a user is changing the display language.
What I did until now:
in xaml:
<ComboBox x:Name="cmb"/>
and in C#:
public class MyCmbItem
{
public int Index { get; set; }
public string Text { get; set; }
}
private ObservableCollection<MyCmbItem> LoadText()
{
ObservableCollection<MyCmbItem> _result = new ObservableCollection<MyCmbItem>();
foreach (var _item in _list)
{
//the list is coming from a database read
_result.Add(new MyCmbItem { Index = _item.Value, Text = _res_man_global.GetString(_item.KeyText, _culture) });
}
return _result;
}
public ObservableCollection<MyCmbItem> MyTexts
{
get { return LoadText(); }
set {} //I do not have to add/remove items at runtime so for now I leave this empty
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
...
LoadList(); //this adds values in _list
cmb.ItemsSource = MyTexts; //this populates the combo-box
Here I got stuck and I do not know how to determine the combo-box to refresh the displayed texts. The method must achieve that if I have several windows opened each containing a random number of combo-boxes, when I change the current language all the combo-boxes in all the windows will refresh the displayed list, without affecting other values inside (like the selected item). Does anybody know how this can be done?
Many thanks.
For your xaml UI, the INotifyPropertyChanged interface indicates updates of the viewmodel. You can extend your class like this:
public class MyCmbItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string APropertyName)
{
var property_changed = PropertyChanged;
if (property_changed != null)
{
property_changed(this, new PropertyChangedEventArgs(APropertyName));
}
}
private string _Text;
private string _KeyText;
public int Index { get; set; }
public string Text
{
get { return _Text;}
set {
if (_Text != value)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
public MyCmbItem(string key_text, int index)
{
Index = index;
_KeyText = key_text;
RefreshText();
_res_man_global.LanguageChanged += () => RefreshText();
}
public void RefreshText()
{
Text = _res_man_global.GetString(_KeyText, _culture);
}
}
Your view can simply bind to the Text-property as following:
<DataTemplate DataType="{x:Type local:MyCmbItem}">
<TextBlock Text="{Binding Path=Text}"/>
</DataTemplate>
Note: I assumed that your language class is global and has some kind of language-changed notification event.

Categories

Resources