I am fairly new to MVVM, so bear with. I have a view model class that has a public property implemented as so:
public List<float> Length
{
get;
set;
}
In my XAML for the view, I have several text boxes, with each one bound to a specific element in this Length list:
<TextBox Text="{Binding Length[0], Converter=DimensionConverter}" />
<TextBox Text="{Binding Length[2], Converter=DimensionConverter}" />
<TextBox Text="{Binding Length[4], Converter=DimensionConverter}" />
The DimensionConverter is a IValueConverter derived class that formats the values like a dimension (i.e. 480.0 inches becomes 40'0" in the text box on screen), and back again (i.e. takes 35'0" for a string and yield 420.0 inches for the source)
My issue: I need to be able to validate each value in the List as it is changed in the associated TextBox. For some, I may need to modify other values in the List depending on the entered value (i.e. change the float at Length[0] will change the value at Length[4] and update the screen).
Is there any way to re-work the property to allow for an indexer? Or, do I need to create individual properties for each item in the List (which really makes the List unnecessary)? Essentially, since I already have the collection of float, I was hoping to be able to write MVVM code to validate each item as it is modified.
Thoughts? (and, thanks in advance)
You can use an ObservableCollection<float> instead of a List<float>, and handle the CollectionChanged event to detect when the user changes a value.
Wouldn't something like this:
<ItemsControl ItemsSource="{Binding Length}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=TwoWay, Converter=DimensionConverter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Be close to what you want?
It will display the entire list, and allow the user to modify the values, which will be returned straight back to the list, as long as your IValueConverter implements ConvertBack.
Then do as Thomas said to validate, or implement an ObservableLinkedList
What you do at the moment looks dirty already and it's barely a few lines of code..
It would be great if you can have a class which implements INotifyPropertyChanged to have the properties provided the length of list is constant.
if you want validate your text input with mvvm then create a model that you can youse at your viewmodel
public class FloatClass : INotifyPropertyChanged
{
private ICollection parentList;
public FloatClass(float initValue, ICollection pList) {
parentList = pList;
this.Value = initValue;
}
private float value;
public float Value {
get { return this.value; }
set {
if (!Equals(value, this.Value)) {
this.value = value;
this.RaiseOnPropertyChanged("Value");
}
}
}
private void RaiseOnPropertyChanged(string propName) {
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
at your viewmodel you can use the model like this
public class FloatClassViewmModel : INotifyPropertyChanged
{
public FloatClassViewmModel() {
this.FloatClassCollection = new ObservableCollection<FloatClass>();
foreach (var floatValue in new[]{0f,1f,2f,3f}) {
this.FloatClassCollection.Add(new FloatClass(floatValue, this.FloatClassCollection));
}
}
private ObservableCollection<FloatClass> floatClassCollection;
public ObservableCollection<FloatClass> FloatClassCollection {
get { return this.floatClassCollection; }
set {
if (!Equals(value, this.FloatClassCollection)) {
this.floatClassCollection = value;
this.RaiseOnPropertyChanged("FloatClassCollection");
}
}
}
private void RaiseOnPropertyChanged(string propName) {
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
here is the xaml example
<ItemsControl ItemsSource="{Binding Path=FloatClassViewmModel.FloatClassCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay, Converter=DimensionConverter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
hope this helps
Related
I have an ItemsControl that should display the values of some properties of an object.
The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.
What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the question marks are the points where I don't know how to create the binding.
I initially tried with a MultiValueConverter:
<TextBlock Grid.Column="1" Text="{Binding}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource getPropertyValue}">
<Binding Path="Instance" Mode="OneWay"/>
<Binding Path="PropertyName" Mode="OneWay"/>
</MultiBinding>
</TextBlock.DataContext>
</TextBlock>
The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.
But if the property value changes, this change is not notified and the displayed value remains unchanged.
I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.
Edit:
For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:
public class InstanceProperty : INotifyPropertyChanged
{
//[.... INotifyPropertyChanged implementation ....]
public INotifyPropertyChanged Instance { get; set; }
public string PropertyName { get; set; }
}
Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.
The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.
Thanks
Ok, thanks to #GazTheDestroyer comment:
#GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project
I found a solution: editing the ViewModel class InstanceProperty like this
added a PropertyValue property
listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value
here is the new, complete, ViewModel class:
public class InstanceProperty : INotifyPropertyChanged
{
#region Properties and events
public event PropertyChangedEventHandler PropertyChanged;
private INotifyPropertyChanged FInstance = null;
public INotifyPropertyChanged Instance
{
get { return this.FInstance; }
set
{
if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
this.FInstance = value;
if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
this.CheckProperty();
}
}
private string FPropertyName = null;
public string PropertyName
{
get { return this.FPropertyName; }
set
{
this.FPropertyName = value;
this.CheckProperty();
}
}
private System.Reflection.PropertyInfo Property = null;
public object PropertyValue
{
get { return this.Property?.GetValue(this.Instance, null); }
}
#endregion
#region Private methods
private void CheckProperty()
{
if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
{
this.Property = null;
}
else
{
this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
}
this.RaisePropertyChanged(nameof(PropertyValue));
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.PropertyName)
{
this.RaisePropertyChanged(nameof(PropertyValue));
}
}
private void RaisePropertyChanged(string propertyname)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion
}
and here is the XAML:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am writing a WPF program write a program on .NET 4.5 which will hold a lot of settings inside and I am faced with several problems.
For example, I have a camera and I need to create another instance of that camera settings at runtime. For XAML page I have a lot of bindings and now for the second instance I need to clear them an use bindings for new instance of that class in which I hold properties for that settings (If I am thinking correctly, of course) So, I have 2 questions:
How do I change my binding so that I can write the minimum amount of code possible (please, keep in mind that I don't know how many instances will be created)?
How I can create second, third, etc. instances of a class and to lose objects in memory because I need to hold every instance of each class during runtime and just change bindings while switching between these instances.
Create a view model that manages and exposes the settings for you. Use an additional property to provide the currently selected settings:
public class CameraSettings
{
public string Title { get; set; }
public bool Grayscale { get; set; }
}
public class CameraViewModel : INotifyPropertyChanged
{
private CameraSettings _SelectedSettings;
private List<CameraSettings> _Settings;
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<CameraSettings> Settings
{
get { return _Settings; }
}
public CameraSettings SelectedSettings
{
get { return _SelectedSettings; }
set
{
if (_SelectedSettings != value)
{
_SelectedSettings = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedSettings"));
}
}
}
}
public CameraViewModel()
{
_Settings = new List<CameraSettings>()
{
{ new CameraSettings() { Title = "BlackWhite", Grayscale = true } },
{ new CameraSettings() { Title = "TrueColor", Grayscale = false } }
};
}
}
Then you can bind your view to this view model. Example view:
<Window.DataContext>
<local:CameraViewModel />
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Settings}" SelectedItem="{Binding SelectedSettings, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Title}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="{Binding SelectedSettings.Grayscale}" />
</StackPanel>
I have a List<String> in a custom user control. Whenever the List<String> is modified, I want the custom user control to have checkboxes for every item in the List<String>. My original idea (being used to Java) was to just add the checkboxes and remove them directly.
But... I know C# can do better than that. Is there some way I can "bind" the strings to the UI so they show up as checkboxes? (or any other method that works?)
You can do that easily by binding an ItemsControl to the list of strings:
<ItemsControl ItemsSource="{Binding Strings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
However that's not very useful, because you have no easy way to access the state of the CheckBox... A better option would be to wrap the string in a class that also exposes a bool property:
public class CheckableItem<T> : INotifyPropertyChanged
{
public CheckableItem(T value)
{
_value = value;
}
private T _value;
public T Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You can then expose a list of CheckableItem<string> instead of a list of strings, and change the XAML as follows:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Value}" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you need to test if the CheckBox is checked, just test the IsChecked property in the CheckableItem<T> class.
If you need the CheckBox to have 3 states (checked/unchecked/indeterminate), just declare IsChecked as bool? instead of bool.
I have one main outstanding issue, I know now how to databind to lists and individual items, but there is one more problem I have with this, I need to bind a listbox to some properties that are in a Class.
For Example I have two Properties that are bound in some XAML in a class called Display:
Public Property ShowEventStart() As Visibility
Public Property ShowEventEnd() As Visibility
I can use these in my XAML but I want them to be in a ListBox/Drop-down List, how can I have my properties show in this list and be able to change their values, does it have to be a List to be in a List?
I just want to be able to modify these properties from a Drop-down list to toggle the Visibility of the ShowEventStart and ShowEventEnd Property Values using the Checkboxes in the Drop-down List.
Plus this must be a Silverlight 3.0 solution, I cannot figure out how to have something that can be bound in the XAML which is not a list and then bound it as a list to modify these items!
I just need a list of Checkboxes which alter the values of the Class Properties such as ShowEventStart and ShowEventEnd, there are other properties but this will be a start.
You can create a PropertyWrapper class and in you window code behind expose a property that returns a List<PropertyWrapper> and bind your ListBox.ItemsSource to it.
public class PropertyWrapper
{
private readonly object target;
private readonly PropertyInfo property;
public PropertyWrapper(object target, PropertyInfo property)
{
this.target = target;
this.property = property;
}
public bool Value
{
get
{
return (bool) property.GetValue(target, null);
}
set
{
property.SetValue(target, value, null);
}
}
public PropertyInfo Property
{
get
{
return this.property;
}
}
}
your window code behind:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
properties = new List<PropertyWrapper>
{
new PropertyWrapper(this, typeof(Window1).GetProperty("A")),
new PropertyWrapper(this, typeof(Window1).GetProperty("B")),
};
this.DataContext = this;
}
private List<PropertyWrapper> properties;
public List<PropertyWrapper> Properties
{
get { return properties; }
}
private bool a;
private bool b = true;
public bool A
{
get
{
return a;
}
set
{
if (value != a)
{
a = value;
NotifyPropertyChange("A");
}
}
}
public bool B
{
get
{
return b;
}
set
{
if (value != b)
{
b = value;
NotifyPropertyChange("B");
}
}
}
protected void NotifyPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(
this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
your window markup:
<ListBox ItemsSource="{Binding Path=Properties}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Property.Name}"/>
<CheckBox IsChecked="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Hope this helps
I tried to mock up something similar. See how this works for you and let me know if I misunderstood the question.
From MainPage.xaml:
<StackPanel Orientation="Horizontal">
<ListBox SelectedItem="{Binding ShowControls, Mode=TwoWay}" x:Name="VisibilityList"/>
<Button Content="Test" Visibility="{Binding ShowControls}"/>
<CheckBox Content="Test 2" Visibility="{Binding ShowControls}"/>
</StackPanel>
The code behind MainPage.xaml.cs:
public partial class MainPage : UserControl
{
VisibilityData visibilityData = new VisibilityData();
public MainPage()
{
InitializeComponent();
VisibilityList.Items.Add(Visibility.Visible);
VisibilityList.Items.Add(Visibility.Collapsed);
this.DataContext = visibilityData;
}
}
And the data class:
public class VisibilityData : INotifyPropertyChanged
{
private Visibility showControls = Visibility.Visible;
public Visibility ShowControls
{
get { return showControls; }
set
{
showControls = value;
OnPropertyChanged("ShowControls");
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
When you run that code you should get a ListBox with the options Visible and Collapsed and when you choose an option you should see the visibility of the button and checkbox is changed to reflect your choice. Let me know if that's not what you were looking for.
After thinking about this the solution occured to me, which is to construct the List in XAML then using a Converter on the Checkboxes to convert their Boolean IsChecked to the Property Visibility Property in the Class.
You can create a list such as:
<ComboBox Canvas.Left="6" Canvas.Top="69" Width="274" Height="25" Name="Display">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventStart,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event Start"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventEnd,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event End"/>
</StackPanel>
</ComboBox>
I can then Bind to the Two Properties I want (this example from the actual application).
I made a CollectionToStringConverter which can convert any IList into a comma-delimited string (e.g. "Item1, Item2, Item3").
I use it like this:
<TextBlock Text="{Binding Items,
Converter={StaticResource CollectionToStringConverter}}" />
The above works, but only once when I load the UI. Items is an ObservableCollection. The text block does not update, and the converter does not get called when I add or remove from Items.
Any idea what's missing to make this work?
The binding is to the property yielding the collection. It will come into effect whenever the collection instance itself changes, not when items in the collection are changed.
There are quite a few ways to achieve the behavior you want, including:
1) Bind an ItemsControl to the collection and configure the ItemTemplate to output the text preceded by a comma if it's not the last item in the collection. Something like:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<TextBlock>
<TextBlock Visibility="{Binding RelativeSource={RelativeSource PreviousData}, Converter={StaticResource PreviousDataConverter}}" Text=", "/>
<TextBlock Text="{Binding .}"/>
</TextBlock>
</ItemsControl.ItemTemplate>
</ItemsControl>
2) Write code in your code-behind to watch the collection for changes and update a separate property that concatenates the items into a single string. Something like:
public ctor()
{
_items = new ObservableCollection<string>();
_items.CollectionChanged += delegate
{
UpdateDisplayString();
};
}
private void UpdateDisplayString()
{
var sb = new StringBuilder();
//do concatentation
DisplayString = sb.ToString();
}
3) Write your own ObservableCollection<T> subclass that maintains a separate concatenated string similar to #2.
Converter will get called only when the Property changes. In this case the 'Items' value is not changing. When you add or remove new items in to the collection the binding part is not aware of that.
You can extend the ObservableCollection and add a new String property in it.Remember to update that property in your CollectionChanged event handler.
Here is the Implementation
public class SpecialCollection : ObservableCollection<string>, INotifyPropertyChanged
{
public string CollectionString
{
get { return _CollectionString; }
set {
_CollectionString = value;
FirePropertyChangedNotification("CollectionString");
}
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
string str = "";
foreach (string s in this)
{
str += s+",";
}
CollectionString = str;
base.OnCollectionChanged(e);
}
private void FirePropertyChangedNotification(string propName)
{
if (PropertyChangedEvent != null)
PropertyChangedEvent(this,
new PropertyChangedEventArgs(propName));
}
private string _CollectionString;
public event PropertyChangedEventHandler PropertyChangedEvent;
}
And your XAML will be like
<TextBlock DataContext={Binding specialItems} Text="{Binding CollectionString}" />