I have got a DataGridComboBoxColumn that I need to translate with the WPFLocalizationExtension.
I have got a static method in my view model, that provides the available values:
public static List<ProfileSegmentType> AvailableSegmentTypes
{
get
{
var availableSegmentTypes = new List<ProfileSegmentType>
{
new ProfileSegmentType { Value = ProfileSegmentTypeEnum.Arc },
new ProfileSegmentType { Value = ProfileSegmentTypeEnum.Line },
};
return availableSegmentTypes;
}
}
The ProfileSegmentType looks like this:
public class ProfileSegmentType
{
public ProfileSegmentTypeEnum Value { get; set; }
public string Label
{
get { return Resources.Localization.ADummyValue; }
}
}
My data grid column definition looks like this:
<DataGridComboBoxColumn Header="..."
ItemsSource="{Binding Source={x:Static viewModels:ProfileGeometryViewModel.AvailableSegmentTypes}}"
SelectedValueBinding="{Binding SegmentType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
DisplayMemberPath="Label" />
Now I get labels translated (right now the dummy value). But when I switch the language, the cells are not updated, of course. How can I achieve is, that the cell contents are updated, when I switch the language? (I also tried to use a value converter, but couldn't get it to work this way)
With the help of your comments, I could solve it. I had to change my ProfileSegmentType like so:
public class ProfileSegmentType : INotifyPropertyChanged
{
public ProfileSegmentType()
{
LocalizeDictionary.Instance.PropertyChanged += (e, a) =>
{
if (a.PropertyName == "Culture")
PropertyChanged(this, new PropertyChangedEventArgs("Label"));
};
}
public ProfileSegmentTypeEnum Value { get; set; }
public string Label
{
get { return Common.Resources.Localization.Add; }
}
public event PropertyChangedEventHandler PropertyChanged = (e, a) => {};
}
Related
So let me preface by saying that I am very new to WPF and MVVM.
I am using the mvvm design pattern for my application. My goal, is that I need to have two combo boxes loaded with content to select from( in this case, units to convert from and to). The content of these combo boxes is determined by a third combo box which determines the type of units to load.
So for example, the first combo box would let the user select a unit type, such as speed or temperature. So if I select temperature, the other two combo boxes would be loaded with a list of temperature units. Likewise if I select speed, then the list in the other two combo boxes would be replaced with units for speed.
I already have a class that handles the from and to conversion. But I'm a little lost with how to start working with these combo boxes. I have only done some basic things with combo boxes like loading content straight in the xaml. I have seen people make lists and somehow bind them but some it was a little overwhelming.
All I need is a good example and explanation to get me started. Would greatly appreciate it.
Everything you need is a ViewModel class to work with the binding.
Each combo box will binding the ItemSources to a Property in the ViewModel. Everytime the selected of the first combo box is change, you will update the data source of the second combo box.
Here is example of the ViewModel class:
namespace WpfApp1
{
class SampleVM : ViewModelBase
{
private ObservableCollection<UnitEntry> _comboBox1ItemSource;
private ObservableCollection<TypeEntry> _comboBoxTypeItemSource;
private int _selectedTypeIndex;
public ObservableCollection<UnitEntry> ComboBoxUnitItemSource
{
get => _comboBox1ItemSource;
set
{
_comboBox1ItemSource = value;
RaisePropertyChange(nameof(ComboBoxUnitItemSource));
}
}
public ObservableCollection<TypeEntry> ComboBoxTypeItemSource
{
get => _comboBoxTypeItemSource;
set
{
_comboBoxTypeItemSource = value;
RaisePropertyChange(nameof(ComboBoxTypeItemSource));
}
}
public int SelectedTypeIndex
{
get => _selectedTypeIndex;
set
{
_selectedTypeIndex = value;
RaisePropertyChange(nameof(SelectedTypeIndex));
//Here where we will handle the data in the second combo box depend on the Type value when it changed
if(value == 0)
{
ComboBoxUnitItemSource = GetDataUnitType1();
}
else
{
ComboBoxUnitItemSource = GetDataUnitType2();
}
}
}
public SampleVM()
{
InitData();
}
private void InitData()
{
//Init Type data
ComboBoxTypeItemSource = new ObservableCollection<TypeEntry>();
TypeEntry type1 = new TypeEntry(0, "Type 1");
TypeEntry type2 = new TypeEntry(1, "Type 2");
ComboBoxTypeItemSource.Add(type1);
ComboBoxTypeItemSource.Add(type2);
//Selected Index set to default by 0
SelectedTypeIndex = 0;
}
private ObservableCollection<UnitEntry> GetDataUnitType1()
{
//Get your real data instead of fake data below
ObservableCollection<UnitEntry> data = new ObservableCollection<UnitEntry>();
for (int i = 0; i < 5; i++)
{
UnitEntry unitEntry = new UnitEntry(i, $"Type 1 - Entry: {i}");
data.Add(unitEntry);
}
return data;
}
private ObservableCollection<UnitEntry> GetDataUnitType2()
{
//Get your real data instead of fake data below
ObservableCollection<UnitEntry> data = new ObservableCollection<UnitEntry>();
for (int i = 0; i < 5; i++)
{
UnitEntry unitEntry = new UnitEntry(i, $"Type 2 - Entry: {i}");
data.Add(unitEntry);
}
return data;
}
}
public class TypeEntry
{
public int ID { get; set; }
public string Name { get; set; }
public TypeEntry(int id, string name)
{
ID = id;
Name = name;
}
}
public class UnitEntry
{
public int ID { get; set; }
public string Name { get; set; }
public UnitEntry(int id, string name)
{
ID = id;
Name = name;
}
}
}
And here is the xaml class looks like:
<!-- The "Name" value is the Name property in the Entry class-->
<ComboBox Grid.Row="0"
Grid.Column="0"
Width="200"
Height="30"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedIndex="{Binding SelectedTypeIndex}"
ItemsSource="{Binding ComboBoxTypeItemSource}"/>
<ComboBox Grid.Row="0"
Grid.Column="1"
Width="200"
Height="30"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedIndex="0"
ItemsSource="{Binding ComboBoxUnitItemSource}"/>
Finally, important part, you need to assign the ViewModel to the View class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new SampleVM();
}
}
I have a ComboBox defined like this:
<ComboBox
ItemsSource="{Binding Choices}"
SelectedItem="{Binding Value}"
Text="{Binding Text}"
IsEditable="True"
TextSearch.TextPath="Label"
DisplayMemberPath="Label" />
Here is my view Model:
public class ComboBoxViewModel : ViewModelBase
{
private string _selectedCode;
public ReadOnlyObservableCollection<ComboBoxItem> Choices { get; }
public ComboBoxItem Value
{
get { return this.Choices.FirstOrDefault(choice => choice.Code == _selectedCode); }
set
{
this.SetCode(value?.Code)
}
}
public string Text
{
get { return this.Value?.Label ?? _selectedCode; }
set
{
// Only set the code if no pre-defined code can be selected
if (this.Value == null)
{
this.SetCode(value)
}
}
}
public ComboBoxViewModel()
{
this.Choices = [..];
}
public bool SetCode(string code)
{
if (_selectedCode != code)
{
_selectedCode = code;
// Tried all the combination with/without/different order with no change
this.RaisePropertyChanged(nameof(this.Value));
this.RaisePropertyChanged(nameof(this.Text));
}
}
}
public class ComboBoxItem
{
public string Code { get; }
public string Label { get; }
public ComboBoxItem(string code, string label)
{
this.Code = code;
this.Label = label;
}
}
The Choices collection is initialized with some pair: Code,Label. I want to display the Label to the user and use the Code in my business layer. I also want my user to input its own code in the ComboBox (this is why the IsEditable dependency property is set to True and why I also bind Text on my ViewModel).
Everythings works fine when directly bind my ViewModel on the Control. The _selectedCode is updated prioritary with the selected Choices element or with the manual input if necessary.
My problem occurs when I pre-set the _selectedCode using the SetCode method. The Value property is no longer updated when I chose a new existing Choice in the ComboBox...
Is it possible to bind both SelectedItem and Text of a ComboBox? Do you have an idea why the bound properties are not updated after a programmatic initialization? It is like the event is not fired anymore...
Basic question from a novice. I've been stuck on this and have read through a lot of material and several similar questions on SO; hopefully not a completely duplicate question. I simplified the code as much as I know how to.
I'm trying to make the ListView show a filtered ObservableCollection) property (as the ItemsSource?), based on the selection in the ComboBox.
Specifically, which "meetings" have this "coordinator" related to it.
I'm not seeing any data errors in the output while it's running and debugging shows the properties updating correctly, but the ListView stays blank. I'm trying to avoid any code-behind on the View, there is none currently.
Thanks!
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Meeting> meetings;
public ObservableCollection<Meeting> Meetings
{
get
{
return meetings;
}
set
{
meetings = value;
OnPropertyChanged("ListProperty");
OnPropertyChanged("Meetings");
}
}
private string coordinatorSelected;
public string CoordinatorSelected
{
get
{
return coordinatorSelected;
}
set
{
coordinatorSelected = value;
Meetings = fakeDB.Where(v => v.CoordinatorName == CoordinatorSelected) as ObservableCollection<Meeting>;
}
}
private ObservableCollection<string> comboProperty = new ObservableCollection<string> { "Joe", "Helen", "Sven" };
public ObservableCollection<string> ComboProperty
{
get
{
return comboProperty;
}
}
private ObservableCollection<Meeting> fakeDB = new ObservableCollection<Meeting>() { new Meeting("Joe", "Atlas"), new Meeting("Sven", "Contoso"), new Meeting("Helen", "Acme") };
public ObservableCollection<Meeting> ListProperty
{
get
{
return Meetings;
}
}
public class Meeting
{
public string CoordinatorName { get; set; }
public string ClientName { get; set; }
public Meeting(string coordinatorName, string clientName)
{
CoordinatorName = coordinatorName;
ClientName = clientName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window.Resources>
<local:ViewModel x:Key="VM"></local:ViewModel>
</Window.Resources>
<DockPanel DataContext="{StaticResource ResourceKey=VM}">
<ComboBox Margin="10" ItemsSource="{Binding ComboProperty}" SelectedItem="{Binding CoordinatorSelected}" DockPanel.Dock="Top"/>
<ListView Margin="10" ItemsSource="{Binding ListProperty, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="ClientName"/>
</DockPanel>
Update:
This seems to show that the lambda is returning a Meeting object but the assignment to Meetings is failing. Is this an error in casting maybe?
Thanks again.
You always have to change a property's backing field before you fire a PropertyChanged event. Otherwise a consumer of the event would still get the old value when it reads the property.
Change the Meetings property setter like this:
public ObservableCollection<Meeting> Meetings
{
get
{
return meetings;
}
set
{
meetings = value;
OnPropertyChanged("ListProperty");
OnPropertyChanged("Meetings");
}
}
I believe I've found two solutions to the same problem. The error pointed out #Clemens was also part of the solution. The Meetings property problem is solved if I change ListProperty and Meetings to IEnumerable. Alternatively this approach without changing the type, which I believe invokes the collection's constructor with the filtered sequence as an argument.
set
{
coordinatorSelected = value;
var filteredList = fakeDB.Where(v => v.CoordinatorName == coordinatorSelected);
Meetings = new ObservableCollection<Meeting>(filteredList);
OnPropertyChanged("ListProperty");
}
I am really struggling with data binding and the MVVM Methodology, though I like the concept I am just struggling. I have created a WPF for that has multiple comboboxes and a button. The first combobox will list database instance names. the remaining comboboxes will be populated after the button is clicked. Since I am having issues with the first, database instances, combobox I will only show my code for that. When the application starts up the combobox is loaded and the first item is selected, as expected. The issue is when I select a new name my method that I expect to get called does not. Can someone help me to understand why my method public DBInstance SelectedDBInstance is not getting executed when I have this in my XAML, SelectedValue="{Binding SelectedDBInstance, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}?
Here is my XAML for the database instances combobox. One question I have here is the "value" fpr SelectedValuePath, if I change it to say "DBInstanceName" it does not work.
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedValue="{Binding SelectedDBInstance, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="value" DisplayMemberPath="DBInstanceName"/>
Here is my ViewModel Code:
namespace DatabaseTest.ViewModel
{
class RLFDatabaseTableViewModel : INotifyPropertyChanged
{
Utilities dbtUtilities = new Utilities();
public RelayCommand LoadDBInfoCommand
{
get;
set;
}
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
}
public ObservableCollection<DBInstance> DBInstances
{
get;
set;
}
public void LoadDBInstances()
{
ObservableCollection<DBInstance> dbInstances = new ObservableCollection<DBInstance>();
DataTable dt = SmoApplication.EnumAvailableSqlServers(false);
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2012ci" });
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2014ci" });
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
dbInstances.Add(new DBInstance { DBInstanceName = dr["Name"].ToString() });
}
}
DBInstances = dbInstances;
}
private DBInstance _selectedDBInstance;
public DBInstance SelectedDBInstance
{
get
{
return _selectedDBInstance;
}
set
{
_selectedDBInstance = value;
RaisePropertyChanged("SelectedDBInstance");
//ClearComboBoxes();
}
}
}
}
Here is my Model code. When I step through the code this method, public string DBInstanceName, gets executed multiple time. I do not know why and it is seems wasteful to me.
namespace DatabaseTest.Model
{
public class RLFDatabaseTableModel { }
public class DBInstance : INotifyPropertyChanged
{
private string strDBInstance;
public override string ToString()
{
return strDBInstance;
}
public string DBInstanceName
{
get
{
return strDBInstance;
}
set
{
if (strDBInstance != value)
{
strDBInstance = value;
RaisePropertyChanged("DBInstanceName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You should bind the SelectedItem property of the ComboBox to the SelectedDBInstance property and get rid of the SelectedValuePath:
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedItem="{Binding SelectedDBInstance, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="DBInstanceName"/>
The SelectedValuePath property is only used when you want to bind to a source property that is not of the same type as the item in the ItemsSource collection.
To select an item initially you should set the SelectedDBInstance property to an item that is present in the DBInstances collection:
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
SelectedDBInstance = DBInstances[0]; //selected the first item
}
In my wpf application, I am using Prism library to autowire my viewmodel into my view. I have this working for simple properties which automatically binds view and model. Now I am trying to bind an ObservableCollection<T> data to bind into DataGrid with no luck. Below is my structure for current scenario.
ConfigurationDetails.cs
public class ConfigurationDetails:BindableBase
{
private int _id;
public int Id { get { return _id; } set { SetProperty(ref _id, value); } }
private string _configFName;
private string _configSName;
private string _configUName;
public string ConfigFName { get { return _configFName; } set { SetProperty(ref _configFName, value); } }
public string ConfigSName { get { return _configSName; } set { SetProperty(ref _configSName, value); } }
public string ConfigUName { get { return _configUName; } set { SetProperty(ref _configUName, value); } }
}
ConfigurationWindowViewModel.cs
public class ConfigurationWindowViewModel : BindableBase
{
public ConfigurationWindowViewModel()
{
ConfigDetails = new ObservableCollection<ConfigurationDetails>();
}
private ObservableCollection<ConfigurationDetails> _configDetails;
public ObservableCollection<ConfigurationDetails> ConfigDetails { get { return _configDetails; } set { SetProperty(ref _configDetails, value); } }
}
ConfigurationWindow.xaml
<UserControl x:Class="MyApp.Views.ConfigurationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
HorizontalContentAlignment="Center">
....
<DataGrid ItemsSource="{Binding ConfigDetails}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="F NAME" Width="*" Binding="{Binding Path=ConfigFName}"/>
<DataGridTextColumn Header="S NAME" Width="*" Binding="{Binding Path=ConfigSName}"/>
<DataGridTextColumn Header="U NAME" Width="*" Binding="{Binding Path=ConfigUName}"/>
</DataGrid.Columns>
</DataGrid>
....
</UserControl>
ConfigurationWindow.xaml.cs
public ConfigurationWindow()
{
InitializeComponent();
using (_db = new MyEntities())
{
var configRecords = _db.tblConfigs.ToList().Select(x => new ConfigurationDetails()
{
ConfigFName = x.ConfigFName,
ConfigSName = x.ConfigSName,
ConfigUName = x.ConfigUName,
Id = x.ConfigID
});
model.ConfigDetails = new ObservableCollection<ConfigurationDetails>(configRecords);
//model.ConfigDetails will have records assigned to it when debugged
}
}
}
But still, I don't see any records displayed in my DataGrid. What is it am missing here. I have also used ViewModelLocator.AutoWireViewModel and it has been working flawlessly for other model properties. Hope to get some help here.
Update - I have my ConfigurationWindowViewModel.cs placed in ViewModels folder. Sorry for missing that to mention.
Where are you adding the ConfigurationDetails objects to the ConfigDetails collection? Try to add some items in the constructor of the ConfigurationWindowViewModel class:
public class ConfigurationWindowViewModel : BindableBase
{
public ConfigurationWindowViewModel()
{
ConfigDetails = new ObservableCollection<ConfigurationDetails>();
//add some items...:
ConfigDetails.Add(new ConfigurationDetails() { ConfigFName = "F", ConfigSName = "S", ConfigUName = "U" });
ConfigDetails.Add(new ConfigurationDetails() { ConfigFName = "F", ConfigSName = "S", ConfigUName = "U" });
ConfigDetails.Add(new ConfigurationDetails() { ConfigFName = "F", ConfigSName = "S", ConfigUName = "U" });
}
private ObservableCollection<ConfigurationDetails> _configDetails;
public ObservableCollection<ConfigurationDetails> ConfigDetails { get { return _configDetails; } set { SetProperty(ref _configDetails, value); } }
}
And for the autowire functionality in Prism to work out-of-the-box you need to follow the default naming conventions, i.e. your ConfigurationWindowViewModel class should be located in a "ViewModels" folders (and in the .ViewModels namespace) in the root folder of your project. Please refer to Brian Lagunas' blog post for more information about this and how you could change the default conventions by calling the ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver method in your Bootstrapper or App.xaml.cs: http://brianlagunas.com/getting-started-prisms-new-viewmodellocator/
Also make sure that your implements the IView interface:
public partial class ConfigurationWindow() : Window, IView
{
...
}
Well... First of all ObservableCollection implements INotifyPropertyChanged, so you don't have to do that.
Then this is enough:
public ObservableCollection<ConfigurationDetails> ConfigDetails { get; set; }
public ConfigurationWindow()
{
InitializeComponent();
using (_db = new MyEntities())
{
var configRecords = _db.tblConfigs.ToList().Select(x => new ConfigurationDetails()
{
ConfigFName = x.ConfigFName,
ConfigSName = x.ConfigSName,
ConfigUName = x.ConfigUName,
Id = x.ConfigID
});
foreach(var config in configRecords)
{
model.ConfigDetails.Add(config);
}
}
}
}