Here is my situation. I have multiple user controls in a canvas. This canvas is saved out to a xaml file using XamlWriter. As most of you know, bindings are not saved using this method, so when I use XamlReader and read the user control back in, the binding no longer exists.
For a simple test, I have been trying to re-bind a ComboBox ItemsSource loaded from a XAML file (which is what I'm having issues with inside the User Control). I have tried implementing INotifyPropertyChanged, however, my variable:
public event PropertyChangedEventHandler PropertyChanged
is always null when I try and set the ComboItemsProperty:
public ObservableCollection<string> ComboItemsProperty
{
get { return ComboItems; } //Field
set
{
ComboItems = value;
OnPropertyChanged("ComboItemsProperty");
}
So, my ultimate goal is to load a xaml file and then add items to a ComboBox's ItemsSource and then have the ComboBox update with the new items.
Am I going about this the wrong way? Could someone provide me with a simple working example of this?
EDIT:
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged
if (PropertyChanged != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm fairly certain it has something to do with loading the XAML and the binding no longer being set. I have tried setting the binding, but no luck.
2nd EDIT:
I think I'm 99% sure that the binding is the cause. My OnPropertyChanged works fine as long as I didn't load the combobox from a file. I have tried setting the binding as follows:
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this.ComboItemsProperty; //Not sure about this line.
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBiding(ComboBox.ItemsSourceProperty, bind);
Confirmed. When I bring back in a simple combobox, my attempt to bind it is not working. It must be something in the code above.
bind.Source needs to point to the object containing ComboItemsProperty, not the property itself.
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBiding(ComboBox.ItemsSourceProperty, bind);
You can check the binding succeeded with:
if (GetBindingExpression(ComboBox.ItemsSourceProperty).Status != BindingStatus.Active)
{
//binding didn't work
}
Related
This is probably not that difficult but as a wpf/mvvm beginner I'm still struggling with some basics.
I have a combobox (acts like a filter) and a griddata in a view. I use MMVM and databinding. On startup griddata is populated and the combobox's selection is set - that works fine. I'd like to replace the content of the griddata when the selection in the combobox changes. The combobox selected value is bound to a property in my viewmodel so I know when it changes and I can easily replace the content of the collection (which is IList) bound to the griddata. However I do not know how to force the griddata to 'refresh' itself in the view using mvvm bindings.
I was considering using ObservableCollection<> but from what I've read it looks like replacing the content is not that simple either. I do not really need the view to know when a single item in the collection changes - I will always replace the whole content of the list.
I'd appreciate any suggestions.
You can implement INotifyPropertyChanged in you ViewModel and call it when the List changes.
Example:
public class ViewModel : INotifyPropertyChanged
{
private List<MyObject> _myList;
public List<MyObject> MyList
{
get { return _myList; }
set { _myList = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Now when you replace MyList e.g. MyList = newlist it will tell the UI to update whatever is bound to MyList
I have spent several days on this issue and can't seem to get it to work.
I have a user control that is saved out to a xaml file with the following code:
StringBuilder outstr = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
XamlDesignerSerializationManager dsm = new
XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings));
dsm.XamlWriterMode = XamlWriterMode.Expression;
System.Windows.Markup.XamlWriter.Save(test1, dsm);
String saveCard = outstr.ToString();
File.WriteAllText("inputEnum.xaml", saveCard);
Xaml for the user control:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding DescriptionWidth}" />
<ColumnDefinition Width="{Binding ValueWidth}" />
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="1" Background="White" FontSize="{Binding FontSizeValue}" Width="Auto"
Padding="10,0,5,0" ItemsSource="{Binding ComboItemsProperty}" SelectedIndex="{Binding EnumSelectedIndex}">
</ComboBox>
</Grid>
The ItemsSource in the combobox is what is giving me problems.
When I save an instance of this usercontrol out to a file, from my understanding, the {Binding ComboItemsProperty} is lost. So, in the constructor of my usercontrol I have:
public UserInputEnum()
{
InitializeComponent();
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
}
Here is my property and the changed method:
EnumItemsCollection ComboItems = new EnumItemsCollection();
public EnumItemsCollection ComboItemsProperty
{
get { return ComboItems; }
set
{
ComboItems = value;
OnPropertyChanged("ComboItemsProperty");
}
}
public void OnPropertyChanged(string propertyName)
{
getEnumItems(this.ComboItemsProperty, this.EnumSelectedIndex, this.ID, this.SubmodeID);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this.ComboItems, new PropertyChangedEventArgs(propertyName));
}
}
Just a note. EnumItemsCollection is a simple class that inherits off ObservableCollection. There is nothing else to this class. (not sure if this makes a difference).
I think this should work but when when I load the XAML file through the XAMLReader, my combobox items won't update.
EDIT:
I ran a little test on an instance of user control that wasn't loaded from XAML but is in the MainWindow.xaml.
Everything works fine. When I add to the ComboItemsProperty, the combobox updates.
So, I took away the {Binding ComboItemsProperty} and tried to set the binding in the code as above changing 'this' to the instance of the user control. Didn't work. This tells me it is the binding code that is not functioning correctly.
I'm fairly certain is the bind.Source line that is the issue. When it is in a UserControl I am unsure of what to put there.
EDIT:
Code that loads usercontrol from file:
FileStream stream = File.Open("usercontrol.xaml", FileMode.Open, FileAccess.Read);
ComboBox cmb = System.Windows.Markup.XamlReader.Load(stream) as ComboBox;
It loads perfectly fine. The Binding just isn't working(ItemsSource={Binding ComboItemsProperty}) because Bindings aren't saved out.
I load it from a file because this program will have many User Interfaces in a sense. Each one will be loaded by a different person using the program.
You need to the set context of the instance containing your property ComboItemsProperty. So instead of 'this' u should set it to this.DataContext or other class object instance containing the ItemSource property you have defined..
Try this,
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this.DataContext;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
Update
According to Serialization Limitations of XamlWriter.Save available on msdn,
Many design-time properties of the original XAML file may already be optimized or lost by the time that the XAML is loaded as in-memory objects, and are not preserved when you call Save to serialize.
Common references to objects made by various markup extension formats, such as StaticResource or Binding, will be dereferenced by the serialization process.
Conclusion, that I made out now is you cannot directly load the UserControl as whole by Serialization - Deserialization procedure of XAML. I think you can load the object instances by Serialization - Deserialization procedure on the DataContext of the UserControl i.e. the custom list(s) or object(s) you have databound.
I'm playing with ICollectionView right now, and am encountering a problem where I think I understand the "why", but not the "how do I fix it". :)
I have a ComboBox that's databound to an ICollectionView, and it is initially set with the following code:
NameView = CollectionViewSource.GetDefaultView( names); // names is an IEnumerable<string> that comes from a LINQ query
NameView.CurrentChanged += new EventHandler(NameView_CurrentChanged);
Everything works great until I execute a piece of code that generates a new IEnumerable<string> and sets NameView again with the same code as above. Once I do this, CurrentItem is no longer working properly.
I've run into this problem before with ObservableCollection<string> databound to ComboBoxes, and I get around the "unbinding" problem by using Clear() and Add() instead of setting the ObservableCollection<string> property to a new ObservableCollection<string>.
My questions include:
1. If I wanted to be able to just set the property to a new collection, can I re-establish databinding with the new collection somehow? If so, how? If not, can you explain the WPFisms behind why this is fundamentally not possible?
2. What's the best way to deal with changes in an ObservableCollection<string> or ICollectionView? Is my approach of just Clearing and Adding the only way to do it?
When you bind your WPF Controls to ICollectionViews (Happens when the XAML is parsed withing your InitializeComponent-call - You should really define the bindings in XAML!), the Controls subscribe to the required events published by your collection (e.g. CollectionChanged).
Your collection property is just a reference to a memory address. When you bend this to a new collection (i.e. a new address), the DataBinding won't notice. You can't expect the original Collection to publish something like "IAmOuttaHere", and clearly the controls wouldn't listen to a new collection saying "I'm the new guy". But if I see this correctly, your snippet does nothing but add an eventhandler to the CurrentChanged (meaning your observe when some other item in the Combobox is being selected)
Binding is all about notification, so - as long as you don't tell your controls that the collection has been exchanged, they will stick to the initial collection. Please try to implement INotifyPropertyChanged like so:
public class ViewModel : INotifyPropertyChanged
{
private ICollectionView myCollection;
public ICollectionView MyCollection
{
get
{
return this.myCollection;
}
set
{
this.myCollection = value;
this.OnPropertyChanged("MyCollection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void ExchangeCollection()
{
this.MyCollection = new CollectionView(....)
}
}
Any bindings should be made to MyCollection. Although, personally, I don't define ICollectionViews myself, since they are not really as nice to work with as for example a nifty IList and they are auto-wrapped around any collection anyway as soon as a binding is defined.
Hope this helps
Sebi
in my Silverlight 4 Application I have an ObservableCollection that I data bind to two different listboxes. The listboxitems showing the content of the MyClass-Object. When I add an item to the ObservableCollection, the new item is displayed in both listboxes properly.
I have set the Binding Mode to two way, so that editing the listboxitems will update the model automatically. This works so far. My problem is, that the content of the other listbox doesn't update with the updated model. Adding a new item will properly show up on the other listbox, but the updates of the content (which I checked happens) won't.
Any ideas how to achieve: The content of the other listbox should update automatically, when the I update the content in one listbox.
Thanks in advance,
Frank
To expand on what luke said your class needs to implement INotifyPropertyChanged and your properties need to throw the PropertyChanged event in their setters.
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; // This may be named wrong
private string _myString = null;
public string MyString
{
get { return _myString; }
set
{
if(value == _myString)
return;
_myString = value;
var eh = PropertyChanged;
if(eh != null)
eh(this, new PropertyChangedEventArgs("MyString"));
}
}
}
The MyString property will notify the UI that it has changed which will trigger the binding to update.
you need to make sure that your objects in the observable collection implement INotifyPropertyChanged and they should post change events when your content properties change.
Could somebody please explain to me what happens here?
I am creating a binding in code.
The target object is a UserControl
The target property is a boolean DependencyProperty
The source object is a FrameworkElement and implements INotifyPropertyChanged
The source property is of type ObservableCollection
What happens:
The binding is created in code, the result BindingExpressionBase looks fine, the mode is OneWay, the target value gets set correctly (at this time)
Binding b = new Binding();
b.Path = "SourceProperty";
b.Source = SourceObject;
BindingExpressionBase e = this.SetBinding(TargetProperty, b);
The source property then gets changed as a result of another databinding. The UserControl tries to fire the PropertyChanged event.
....but nobody is listening. PropertyChanged is null.
I am sure that nothing else is assigned to the target property, so it should still be bound. Why is the binding not listening for the PropertyChanged event?
Alright, I found the answer myself. This is a bug in Silverlight..
The code that does the following
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
must be directly on the class that you bind to, not on its ancestor. I had it inside a FirePropertyChanged() method on a base class and moving it to the derived class made the difference.
For anyone else who is experiencing this: make sure you are implementing INotifyPropertyChanged on your ViewModel!