WPF/MVVM: Dynamically add checkboxes to 2 ColumnGrid based on available Enums - c#

I'm currently build a gui for a logger. The logger class has a enum Types with a couple of values defining the different log message types.
Now I want to create a gui for this logger. Part of this gui is a 2 column grid where a checkbox for each log message type is available. It shall be used to filter the log. In order to reduce future coding effort, I would like to add a checkbox for each available type of log message dynamically. So when ever a new value is added to Logger.Types the gui adapts automatically.
My xaml of the checkboxes look something like this:
<CheckBox Name="CheckBoxTypeInformation" Grid.Column="0" Grid.Row="1"
Content="{myControls:LocalizationMarkup 'LOGMSG_TYPE_INFORMATION'}"
Command="{Binding FilterTypeCommand}" CommandParameter="Information"
IsChecked="{Binding Path=FilterType[0]}"/>
So there is quite some work to be done for each checkbox:
Insert the cb into the 2 column grid in some manner by defining the Grid.Column and Grid.Row. The logic of it is not the problem
Set the Content with a custom LocalizationMarkup
Bind a command with dynamic CommandParameter. Here I would like to pass the actual enum value. But I was not able to do so in xaml atm.
Bind the IsChecked Property to an index in an array holding the IsChecked Status of all checkboxes.
I'm not quite shure how to handle this. As I understood it correctly I somehow have to add an ObservableCollection<> of a new custom class which has properties for each of the 4 wanted tasks.
I hope somebody can point me into the right direction on how to implement something dynamic as described in a propper mvvm/wpf manner.
Thank you very much!

I would't use a grid for that, but simply a List.
Define a classe named LogLevelViewModel with two properties
private string _logDetails;
public string LogLevelDetails
{
get
{
return _logDetails;
}
set
{
if (_logDetails == value)
{
_logDetails = value;
RaisePropertyChanged(nameof(LogLevelDetails));
}
}
}
private LogLevel _logLevel;
public LogLevel LogLevel
{
get
{
return _logLevel;
}
set
{
if (_logLevel == value)
{
_logLevel = value;
RaisePropertyChanged(nameof(LogLevel));
}
}
}
Then define on your view model a ObservableCollection LogLevelViewModels
On the constructor of the view model, you can populate it With:
foreach (LogLevel level in Enum.GetValues(typeof(LogLevel )))
{
LogLevelViewModels.Add(level );
}
On the XAML make sure to define a DataTemplate for LogLevelViewModel and that's it.

Related

Distinguish if the user or the program changed a value in a wpf datagrid

i have a mvvm application in which i have a DataGrid in Wpf and want to get notified if a user changes a value in a column.
All dataGridColumns have a binding to my viewmodel, which invokes a PropertyChanged Command if it gets changed. Now the Problem is, how i can determine if the property has been changed by the user or by the code? Because i only want to add a note to the corresponding line when it has been changed manually by the user.
The Column of interest is implemented like this in wpf:
<DataGridTextColumn
Header="DIL"
Binding="{Binding DilutionFactor, StringFormat={}{0:n4}}"
Visibility="{Binding Value,
Source={StaticResource DilVis},
Converter={StaticResource BooleanToVisibilityConverter}}"
IsReadOnly="False"/>
Which is bound to the ViewModel Property:
public double DilutionFactor
{
get { return _dilutionFactor; }
set
{
_dilutionFactor = value;
Update(); // PropertyChanged Command
UpdatePipetteVolumes(); // function to update corresponding volumes
}
}
Is there a event or anything i can use to trigger a method when the user changes the value of the DIL column, which is not triggered when the code updates the value?
You could set a boolean flag each time before you programmatically change the value. Then in the property setter you can check that property to see if the user invoked the change. However, this method might need a lot of code changes for heavily used properties.
Another way:
Add a second property which just sets and returns the existing property. Then use that new property for the datagrid binding:
public double DilutionFactorUser
{
get { return this.DilutionFactor; }
set
{
this.DilutionFactor = value;
// Here comes the code that is only executed on user-invoked changes
}
}
public double DilutionFactor
{
get { return _dilutionFactor; }
set
{
_dilutionFactor = value;
Update(); // PropertyChanged Command
UpdatePipetteVolumes(); // function to update corresponding volumes
}
}
Set up your Datagrid to bind to DilutionFactorUser
You could also use the DataGrid.CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) event instead of looking the Property. e.Row.Item will have the data you are binding to.

Get TextBox name from WPF application with MVVM model in helper class

So I am trying to build a MVVM application with WPF and i am stuck with this problem:
In the view class, I have a list of textBoxes that I want to acces in order to validate the inputs.
So I managed to do this by writing in the view class, and it works:
var list = mainGrid.Children.OfType<TextBox>().ToList();
var dictOfTb = new Dictionary<string, string>();
foreach (var item in list)
{
dictOfTb.Add(item.Name, item.Text);
}
But the problem is that I am trying to respect the MVVM pattern and I should to this in a helper class, lets call it ModelsPageHelper, and access it from the View, because here what I am trying to do is to only get data from the UI and pass it to the viewModel to get a result, and then to display it.
So in this class I wrote a method,GetValuesFromTextBoxes(List<TextBox> textBoxes) and I am writing the same code, but now I get a message saying that TextBox does not contain a definition for Name.
So the question is, how can I do the same thing in the helper class to acces the names of those textBoxes?
To do MVVM properly you have to stop thinking about code to validate text boxes, and think about a ViewModel that validates itself, and a view that displays the state of the ViewModel.
For example, if you have a property called Foo which must have the value Bar, the code would look like this:
public string FooError { get; private set; }
private string foo;
public string Foo
{
get => return foo;
set
{
foo = value;
if (foo == "Bar") FooError = "";
else FooError = "Foo must be Bar";
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(FooError));
}
}
and your XAML would look something like this:
<TextBox Text={Binding Foo}/>
<TextBlock Text={Binding FooError}/>
Then perhaps your save button could check for errors too.
I'd strongly advise you to look into INotifyDataErrorInfo, which is a great way to organise your errors in a way that WPF can easily display (instead of properties like FooError). It might seem like a lot of work the first time you use it, but it's great once you've got lots of controls and validation rules.

How to control DynamicResource implementation in C#

In my program I would like to implement a DynamicResource from code-behind. Right now I am binding the Content of a Label to a string property in my Data Model...
<Label Content="{Binding DataModel.StringValue}" ... />
Following this question, I have implemented the string in my Data Model like so:
private string _stringValue = (string)Application.Current.Resources["nameOfResource"];
public string StringValue
{
get { return _cartsInSystem; }
set
{
_cartsInSystem = value;
NotifyPropertyChange(() => CartsInSystem);
}
}
I would like to make it so that every time the user changes the Resource Dictionary, this string value updates with the new value.
I am trying to achieve the same effect as something like this:
<Label Content="{DynamicResource nameOfResource}" ... />
Please let me know what I am doing wrong, and how I might correctly implement something like this.
UPDATE 1: As requested by #HighCore, this is an example of my code where I only have access to string values from code-Behind (or C# class)
(This is part of a ViewModel of a TreeView in my MainWindow)
//The "DisplayNames" for these nodes are created here and not accessible through xaml.
//This is because the xaml window has access to this code through it's itemsSource
private HierarchicalVM CreateCartsNode()
{
return new HierarchicalVM()
{
DisplayName = "Carts",
Children =
{
new CartConnection() { ConnectionDataModel = new CartConnectionModel(), DisplayName = "Cart Connection" },
new HierarchicalVM() {
DisplayName = "Cart Types",
Children = {
CreateCartType( new CartConfigModel() { DisplayName = "Default" }, new CartIO_Model() ),
},
Commands = { new Command(OpenAddCart) {DisplayName = "Add..."} }
}
}
};
}
This is the xaml of the above TreeView:
<!-- Tree view items & Functions -->
<TreeView ItemsSource="{Binding DataTree.Data}" ... />
Update 2: I have another perfect example of my problem...
I have a comboBox that has it's itemsSource bound to an ObservableCollection in my Data Model. Like so:
private ObservableCollection<string> _objCollection;
private string _notUsed = "Not Used";
private string _stop = "Stop";
private string _slow = "Slow";
public DataModel()
{
ObjCollection = new ObservableCollection<string>() { _notUsed, _stop, _slow };
}
public ObservableCollection<string> ObjCollection {...}
xaml:
<ComboBox ItemsSource="{Binding DataModel.ObjCollection}" ... />
If I want to make it so that the items in this comboBox change when the resource dictionary is changed, it looks like I'll need to handle it in C# rather than xaml.
After OP's UPDATE 2 and having a chat with him for a different question, I understood he was trying achieve localisation for his application. He would change Resource Dictionaries (for different languages) on the fly, and he wanted his C# code re-read/load values from Application.Current.Resources.
APPROACH ONE
After you changing the Resource Dictionary, You could use something like EventAggregator/Mediator to let other parts of the application (including ViewModels) know about Resource Dictionary change, and they respond to it by re-loading/reading resources/values from Application.Current.Resources
APPROACH TWO
OP doesn't want to introduce any new dependencies like EventAggregator/Mediator. So, I suggested this second approach. I know, it is not pretty, but here it goes..
You could have a global static event instead of EventAggregator/Mediaotr to let other parts of the application know that you swapped resource dictionary, and they will re-load/read values.
Read this answer about potential problems with static events and their subscriptions.

wpf binding with different source and target

Is is possible to make a binding in WPF whereas the source and target are different properties.
A la something like
Binding="{Binding Source=MySourceProperty, Target=MyTargetProperty}"
As requested an explanation of what I need to do:
The program among other things allows editing of properties that are part of a primary key in the database. If the property just gets changed, then this will either not update the DB value or create a duplicate, depending on how I handle saving the object. A different target would allow this to work (by explicitly specifying what to update by using the 'old' value).
A Binding defined in XAML is always targeting the object and property on which it's defined.
If you define the Binding in code, you can/must specify the source and target explicitly. This is, essentially, how the Binding class works:
Binding binding = new Binding("SourceProperty"); // Sets up the source property
myBinding.Source = mySourceObject; // sets up the source object
targetProperty.SetBinding(TargetType.TargetDepProperty, binding); // This sets the target object/binding
The XAML markup extension for a binding takes care of setting up the target side of the equation automatically, so it's always the object on which you define the binding.
I'll try to answer WHAT you need instead of asked incorrect HOW
"If the property just gets changed, then this will either not update
the DB value or create a duplicate"
In your property setter you should check set { if (this.someMember != value) if the typed in value is has changed:
public event PropertyChangedEventHandler PropertyChanged;
private string someMember;
public int SomeProperty
{
get
{ return this.someMember; }
set
{
if (this.someMember != value)
{
someMember = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SomeProperty"));
}
}
}
As aside-note (or off-topic),
you might find useful the codeproject DataContext in WPF article in its last Download the source code has a sample when updates of one VisualModel's property is reflected (synchronized with updates in the other VM's property)
Immediately after launch:
The text typed in the 1st textbox is reflected in the 2nd textbox and vice versa, the text typed in the 2nd textbox is reflected in the 1st.
The text typed in the 3d textbox is reflected in the 4th textbox (and in textblock content at bottom) and vice versa, the text typed in the 4th textbox is reflected in the 3d (and in textblock content at bottom) .
Note that the download is DataCotext Inner Objects.zip which is unzipped into directory and solution with name Bindingtoclassesstate
In the set of the public property you have access to the old value
key is the old value
value is the proposed value from the binding
you can reject the value that comes from the binding
private string key;
public string Key
{
get { return key; }
set
{
if (key == value) return;
// try data update
bool success = updateDB();
if (success) key = value; // only update if success
}
}
I would combine the above with validation to notify the user if a value was invalid.
Validation Class

Combobox does not change value when a different value is selected

I am trying to migrate a small prototype application I made in WinForms to WPF. I'm having some issues with a combobox in WPF not changing values when I select a different value from the drop-down. Initially, I tried just copying the code that I used in my WinForms app to populate the combobox and determine if a new index had been selected. This is how my WinForms code looked like:
private void cmbDeviceList_SelectedIndexChanged(object sender, EventArgs e)
{
var cmb = (Combobox) sender;
var selectedDevice = cmb.SelectedItem;
var count = cmbDeviceList.Items.Count;
// find all available capture devices and add to drop down
for(var i =0; i<count; i++)
{
if(_deviceList[i].FriendlyName == selectedDevice.ToString())
{
_captureCtrl.VideoDevices[i].Selected = true;
break;
}
}
}
Earlier in the code, I am populating the _deviceList List and the combo box (in Form1_Load to be specific) by looping over the available devices and adding them. I tried the same approach in WPF and could only populate the combo box. When I selected a new value, for some reason the same exact value (the initial device) was being sent into the event code (cmbCaptureDevices_SelectionChanged in my WPF app). I looked around for some tutorials in WPF and found that maybe data binding was my issue, and I tried that out instead. This is my combobox in my XAML file:
<ComboBox ItemsSource="{Binding Devices}" Name="cmbCaptureDevices"
IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding CurrentDevice,
Mode=TwoWay}" Se;ectionChanged="cmbCapturedDevices_SelectionChanged" />
There's more to that XAML definition, but it's all arbitrary stuff like HorizontalAlignment and whatnot. My VideoDevicesViewModel inherits from INotifyPropertyChanged, has a private List<Device> _devices and a private Device _currentDevice. The constructor looks like:
public VideoDevicesViewModel()
{
_devices = GetCaptureDevices();
DevicesCollection = new CollectionView(_devices);
}
GetCaptureDevices simply is the loop that I had in my WinForms app which populates the list with all avaialble capture devices on the current machine. I have a public CollectionView DevicesCollection { get; private set; } for getting/setting the devices at the start of the application. The property for my current device looks like:
public Device CurrentDevice
{
get { return _currentDevice; }
set
{
if (_currentDevice = value)
{
return;
}
_currentDevice = value;
OnPropertyChanged("CurrentDevice");
}
}
OnPropertyChanged just raises the event PropertyChanged if the event isn't null. I'm new to WPF (and pretty new to C# in general, honestly) so I'm not sure if I'm missing something elementary or not. Any idea as to why this combobox won't change values for me?
Discovered the answer on my own here. The unexpected behavior was a result of using the Leadtools Device class. It's a COM component and apparently was not playing nicely with my application. I honestly don't understand why exactly it worked, but I wrapped the Device class in another class and used that instead. As soon as I was using the wrapper class, the combo box functioned as it should.
You are using the assignment operator '=' instead of the equality operator '=='
Change
if (_currentDevice = value)
to
if (_currentDevice == value)
Try the following
if _currentDevice == value ...

Categories