What is the correct way to bind to a value by showing a friendly name from the list of presets also defined in this element?

Got hard. Consider a ViewModel, which consists of a list of objects, where each object defines an int value, and some of these objects also define an ints preset dictionary, packed on a "friendly" line representing this value in the user interface.

Here is an example ...

List<SomeItem> AllItems; public class SomeItem : INotifyPropertyChanged { public SomeItem(int initialValue, Dictionary<int,string> presets) { this.CurrentValue = initialValue; this.Presets = presets; } public int CurrentValue{ get; set; } // Shortened for readability. Assume this property supports INPC public Dictionary<int,string> Presets{ get; private set; } } 

The purpose of the user interface is that if the element does not have presets, the user can enter any value that they want. However, if there are presets, we want to limit them to these values, and also display them in the user interface as friendly names from the dictionary.

Our first attempt was to use TextBox and ComboBox, changing their visibility depending on whether there were presets or not, for example ...

 <ComboBox ItemsSource="{Binding Presets}" DisplayMemberPath="Key" SelectedValuePath="Value" SelectedValue="{Binding CurrentValue, Mode=TwoWay}" Visibility={Binding HasPresets, Converter=...}"> <TextBox Text="{Binding CurrentValue}" Visibility={Binding HasPresets, Converter...}" /> // Assume the inverse of the combo 

... but when we use it in a DataTemplate of a list that supports virtualization, combos sometimes display spaces. I believe because when the item is reused and the DataContext changes, the SelectedValue updates to the ItemsSource, which means there is potentially no predefined value for the match, so the proposed SelectedValue value gets the value from the control and then the ItemsSource, but there is no selected value, therefore shows a space.

My next thought (and what we preferred anyway) was to use only a TextBox that displayed a preset name, but actually tied to Value, and then used the converter to work with its magic and allowed the user to enter either friendly name or actual value. If the user typed something that was not a valid value or preset, we just throw an error. If there were no presets, it will simply act as a cross-cutting indicator.

However, I am not sure how to transfer the presets to the converter. You cannot set the binding on ConverterParameter to pass them that way, and if you use multiple bindings, I’m not sure how to structure the ConvertBack call, because there I also need them to pass, not send back.

I think the correct way is to implement UiValue in the ViewModel, which we would just bind to this ...

 <TextBox Text="{Binding UiValue}" /> 

... then move the code that would be in the converter to the implementation of the getter / setter property or just pass the value if there are no presets. However, it looks like too much logic goes into the ViewModel where it should be in the view (ala a converter or similar.) And again, maybe this is exactly the point of the ViewModel. I do not know. Thoughts are welcome.

+4
source share
2 answers

Personally, I would like to put the converter code in a property, as you suggested ... I see no problems with the presence of code there. Actually, it is probably better than having it in Converter , because then you can also easily test it.

Sorry, this is not a good answer, but I felt that your question deserves at least one.

+2
source

I like your question because it illustrates the way of thinking behind the existence of ViewModel in WPF. Sometimes they seem inevitable.

Converters are designed to be stateless, so it is difficult to pass context variables such as presets . ViewModel is the layer whose responsibility is to prepare the Model for binding purposes. The role of the “model” is to process logic. Thus, a ViewModel can describe in detail the behavior (logic) of a View . This is exactly what you want. Most of the time I don’t need converters at all.

Sometimes it seems more natural that the presentation logic should be in the View , but then the ViewModel seems redundant. However, when this logic is in the ViewModel , it is usually easier to autotest. I would not be afraid to put such things in the ViewModel in general. This is often the easiest (and correct) way.

It has a UiValue property in the ViewModel and handles the conversion there:

 public string UiValue{ get{/*...*/} set{/*...*/} } 

To rephrase, there is no clean way in WPF to replace the property you are binding to. For instance. If you want to have

 <TextBox Text="{Binding IntValue}" /> 

change at some point to:

 <TextBox Text="{Binding PresetValue}" /> 

you are trapped. This is not the case. Better to have a permanent binding, for example

 <TextBox Text="{Binding UiValue}" /> 

and consider the logic behind the UiValue property.

Another possible approach (instead of playing with the visibility of a ComboBox and TextBox ) is to have a DataTemplateSelector, which would decide to create a ComboBox or TextBox for SomeItem . If the presets are empty or empty, select a TextBox DataTemplate , otherwise take a ComboBox. If I'm not mistaken, you will have to examine the FrameworkElement.DataContext property from the selector to find the context ( presets ).

Given your doubts about the ConvertBack method, most often value or Binding.DoNothing returned if you do not need to convert in any of the directions.

+1
source

Source: https://habr.com/ru/post/1501620/


All Articles