Again a have a (probably) simple Problem.
I would like to create a custom UIElement (A Collection of Lines which stay orthogonal).
This UIElement is used as View in my MVVM application.
Here is my code:
class RaOrthogonalLine : Canvas, INotifyPropertyChanged
{
public RaOrthogonalLine()
{
Points.CollectionChanged += Points_CollectionChanged;
}
void Points_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Paint();
}
void Paint()
{
//PaintingStuff! Here I would like to get in!
}
void newLine_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (LineClicked != null)
LineClicked(sender, e);
}
public delegate void LineClickedEventHandler(object sender, MouseButtonEventArgs e);
public event LineClickedEventHandler LineClicked;
public ObservableCollection<RaPoint> Points
{
get
{
return (ObservableCollection<RaPoint>)GetValue(PointsProperty);
}
set
{
SetValue(PointsProperty, value);
RaisePropertyChanged("Points");
}
}
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(ObservableCollection<RaPoint>), typeof(RaOrthogonalLine),
new FrameworkPropertyMetadata(new ObservableCollection<RaPoint>(), new PropertyChangedCallback(PointsPropertyChanged))
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
}
);
private static void PointsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RaOrthogonalLine thisLine = (RaOrthogonalLine)d;
thisLine.Paint();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In my XAML I bind a ObservableCollection of my ViewModel to my ObservableCollection in the UIElement(View).
That works fine.
My problem now is that I do not get notified when the Collection changes (Add/Remove/..) - because then i would need to Repaint it.
I tried to get the Points.CollectionChanged event but it does not fire.
Has anyone a idea?
Thank you!
The problem is that you are adding the CollectionChanged Event handler in the constructor of your Control. In the constructor your Paint property is not binded to the right source yet (Indeed it has the PointsProperty's default value, i.e. an empty collection).
You should add and remove the event handler in the PointsPropertyChanged method. Take a look to this sample code:
public class RaOrthogonalLine : Canvas
{
public INotifyCollectionChanged Points
{
get { return (INotifyCollectionChanged)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
public static readonly DependencyProperty PointsProperty =
DependencyProperty.Register("Points", typeof(INotifyCollectionChanged), typeof(RaOrthogonalLine),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(PointsPropertyChanged))
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
void Points_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Paint();
}
private static void PointsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RaOrthogonalLine raOrthogonalLine = (RaOrthogonalLine)d;
INotifyCollectionChanged newValue = (INotifyCollectionChanged)e.NewValue;
INotifyCollectionChanged oldValue = (INotifyCollectionChanged)e.OldValue;
if (oldValue != null)
{
oldValue.CollectionChanged -= raOrthogonalLine.Points_CollectionChanged;
}
if (newValue != null)
{
newValue.CollectionChanged += raOrthogonalLine.Points_CollectionChanged;
}
raOrthogonalLine.Paint();
}
}
I hope it can help you with your problem.
It might have to do with dependency property but this works just fine for me.
Are you sure you are adding to and removing from the collection (not replacing the collection)?
public class Points
{
void Strings_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("Strings_CollectionChanged");
}
private ObservableCollection<string> strings = new ObservableCollection<string>();
// I think you are better off with just a get
public ObservableCollection<string> Strings { get { return strings; } }
public Points()
{
Strings.CollectionChanged += new NotifyCollectionChangedEventHandler(Strings_CollectionChanged);
Strings.Add("new one");
Strings.Add("new two");
Strings.RemoveAt(0);
}
}
Related
I will explain what I am trying to do first.
I have a quite a few of DataGrids and each DataGrid use different classes for there data type and instead of subscribing an Event handler for each one, I was hoping to make a generic event handler and get the type from from the sender object.
I am using EntityFramework Database First
One example of one of the classes:
public partial class StaffData : INotifyPropertyChanged
{
public long ID { get; set; }
public string StaffNameFirst { get; set; }
public string StaffNameSecond { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
My ViewModel:
VeiwModelBase holds the INotifyPropertyChanged data.
public class MasterViewModel : ViewModelBase
{
public static ObservableCollection<StaffData> MasterDataBinding
{
get { return _mMasterData; }
private set
{
if (value == _mMasterData)
return;
_mMasterData = value;
OnPropertyChanged();
}
}
public MasterViewModel()
{
_mMasterData.CollectionChanged += master_CollectionChanged;
}
public void master_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//asign PropertyChanged event here
}
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Type foo = sender.GetType().GetGenericArguments()[0];
var newRowData = sender as foo;
SaveData(newRowData);
}
private static void SaveData(object newRowData)
{
Type foo = newRowData.GetType().GetGenericArguments()[0];
var originalData = dataBaseEntities.foo.FirstOrDefault(p => p.ID == newRowData.ID);
entities.Entry(originalData).CurrentValues.SetValues(newRowData);
dataBaseEntities.SaveChanges();
}
}
These are the two methods above which I can't seem to figure this out, I have tried countless ways using Getype with not much success (I left my last attempt in hopefully to illustrate what I am trying to do). I have commented out how I am normally going about this:
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Type foo = sender.GetType().GetGenericArguments()[0];
var newRowData = sender as foo;
//var newRowData = sender as StaffData
SaveData(newRowData);
}
//private static void SaveData(StaffData newRowData)
private static void SaveData(object newRowData)
{
Type foo = newRowData.GetType().GetGenericArguments()[0];
var originalData = dataBaseEntities.foo.FirstOrDefault(p => p.ID == newRowData.ID);
//var originalData = dataBaseEntities.StaffData.FirstOrDefault(p => p.ID == newRowData.ID);
entities.Entry(originalData).CurrentValues.SetValues(newRowData);
entities.SaveChanges();
}
When trying to use the variable as a type I get this error,
Error CS0118 'foo' is a variable but is used like a
type
Is there a way to get the type when you don't know which datagrid will implement the PropertyChanged event and use it so as you can make a generic event handler for all the Datagrid controls?
Or am I going about this the wrong way?
Not sure if I really understand your question, but you could check the type of the sender argument at runtime and call an appropriate method like this:
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is StaffData)
{
DoSomething((StaffData)sender);
}
else if (sender is SomeOtherData)
{
DoSomething((SomeOtherData)sender);
}
...
}
private void DoSomething(StaffData data)
{
...
}
private void DoSomething(SomeOtherData data)
{
...
}
However, I'd prefer to have different PropertyChanged handler methods for different sender types.
You cant get the type inside the propertyChanged event handler but you can get the property name from PropertyChangedEventArgs.
Something like:
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "SomePropertyName")
{
//... do your stuf
}
}
I'm completely new to WPF and I'm having problems with ItemsSource updates.
I created a single main window Metro application with tabs (TabItem(s) as UserControl DataContext="{Binding}") in which different data is displayed / different methods used.
What I've found myself struggling with is INotifyPropertyChanged (I wasn't able to understand the solution of my problem from similar examples/questions) interface's concept. I'm trying to make that if new data is entered in a window (which is initialized from one of the UserControl), a ComboBoxin another UserControl (or TabItem) would be automatically updated. Here's what I have:
UserControl1.xaml
public partial class UserControl1: UserControl
{
private userlist addlist;
public UserControl1()
{
InitializeComponent();
fillcombo();
}
public void fillcombo()
{
Fillfromdb F = new Fillfromdb(); // class that simply connects
// to a database sets a datatable as ListCollectionView
addlist = new addlist { List = F.returnlistview() }; // returns ListCollectionView
UsersCombo.ItemsSource = addlist.List;
}
userlist.cs
public class userlist: INotifyPropertyChanged
{
private ListCollectionView _list;
public ListCollectionView List
{
get { return this._list; }
set
{
if (this._list!= value)
{
this._list= value;
this.NotifyPropertyChanged("List");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Registration.xaml (called from another UserControl)
public partial class Registration: MetroWindow
{
public Registration()
{
InitializeComponent();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
Here's the ComboBox's setting in UserControl.xaml:
<ComboBox x:Name="UsersCombo"
ItemsSource="{Binding List, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Since I don't have any programming education/experience a very generic advice/explanation would be very much appreciated.
EDIT: Registration.xaml with propertychanged (still doesn't work):
public partial class Registration : MetroWindow
{
public userlist instance = new userlist();
public ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is save to database
// still don't now what to do with new ListCollectionView from database
}
public void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List; // when new data from database should be loaded?
}
This is where PropertyChanged event comes handy.
Bind the combobox in second xaml page to a List and create a similar property like in first xaml.
In second xaml.cs
public partial class Registration: MetroWindow, INotifyPropertyChanged
{
private userlist instance = new userlist();
private ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List;
//or iterate through the list and add as below
foreach(var item in instance.List)
{
List1.Add(item);
}
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
when we create auto-property of ICollectionView then CurrentChanged event is working properly after refreshing Employee collection.
public ICollectionView EmployeeCollectionView{get; set; }
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
And when we create full-property then CurrentChanged event is not working.
private ICollectionView _employeeCollectionView;
public ICollectionView EmployeeCollectionView
{
get { return _employeeCollectionView; }
set { _employeeCollectionView = value; OnPropertyChanged("EmployeeCollectionView");}
}
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
void EmployeeCollectionView_CurrentChanged(object sender, EventArgs e)
{
var currentEmployee = EmployeeCollectionView.CurrentItem as EmployeeMaster;
}
please suggest if i missing something.
Have you bind EmployeeCollectionView_CurrentChanged event after refreshing employee collection? because if you refreshing employee collection then EmployeeCollectionView_CurrentChanged connection has been lost.
like-
private void Refresh()
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
If you expect EmployeeCollectionView to change (which seems so, otherwise you would not need an OnPropertyChanged, I'd recommend to add the Events in the setter of your property like following:
private ICollectionView _employeeCollectionView;
public ICollectionView EmployeeCollectionView
{
get { return _employeeCollectionView; }
set
{
if (_employeeCollectionView != value)
{
if (_employeeCollectionView != null)
{
_employeeCollectionView.CollectionChanged -= EmployeeCollectionView_CurrentChanged;
}
_employeeCollectionView = value;
_employeeCollectionView.CollectionChanged += EmployeeCollectionView_CurrentChanged;
OnPropertyChanged("EmployeeCollectionView");
}
}
}
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
}
private void EmployeeCollectionView_CurrentChanged(object sender, EventArgs e)
{
var currentEmployee = EmployeeCollectionView.CurrentItem as EmployeeMaster;
}
Suppose, I have objects:
public interface ITest
{
string Data { get; set; }
}
public class Test1 : ITest, INotifyPropertyChanged
{
private string _data;
public string Data
{
get { return _data; }
set
{
if (_data == value) return;
_data = value;
OnPropertyChanged("Data");
}
}
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged;
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
and its holder:
private BindingList<ITest> _listTest1;
public BindingList<ITest> ListTest1 { get { return _listTest1 ?? (_listTest1 = new BindingList<ITest>() { RaiseListChangedEvents = true }); }
}
Also, I subscribe to ListChangedEvent
public MainWindow()
{
InitializeComponent();
ListTest1.ListChanged += new ListChangedEventHandler(ListTest1_ListChanged);
}
void ListTest1_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show("ListChanged1: " + e.ListChangedType);
}
And 2 test handlers:
For adding object
private void AddITestHandler(object sender, RoutedEventArgs e)
{
ListTest1.Add(new Test1 { Data = Guid.NewGuid().ToString() });
}
and for changing
private void ChangeITestHandler(object sender, RoutedEventArgs e)
{
if (ListTest1.Count == 0) return;
ListTest1[0].Data = Guid.NewGuid().ToString();
//if (ListTest1[0] is INotifyPropertyChanged)
// MessageBox.Show("really pch");
}
ItemAdded occurs, but ItemChanged not. Inside seeting proprty "Data" I found that no subscribers for my event PropertyChanged:
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged; // h is null! why??
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
Digging deeper i took reflector and discover BindingList:
protected override void InsertItem(int index, T item)
{
this.EndNew(this.addNewPos);
base.InsertItem(index, item);
if (this.raiseItemChangedEvents)
{
this.HookPropertyChanged(item);
}
this.FireListChanged(ListChangedType.ItemAdded, index);
}
private void HookPropertyChanged(T item)
{
INotifyPropertyChanged changed = item as INotifyPropertyChanged;
if (changed != null) // Its seems like null reference! really??
{
if (this.propertyChangedEventHandler == null)
{
this.propertyChangedEventHandler = new PropertyChangedEventHandler(this.Child_PropertyChanged);
}
changed.PropertyChanged += this.propertyChangedEventHandler;
}
}
Where am I wrong? Or this is known bug and i need to find some workaround?
Thanks!
BindingList<T> doesn't check if each particular item implements INotifyPropertyChanged. Instead, it checks it once for the Generic Type Parameter. So if your BindingList<T> is declared as follows:
private BindingList<ITest> _listTest1;
Then ITest should be inherited fromINotifyPropertyChanged in order to get BindingList raise ItemChanged events.
I think we may not have the full picture from your code here, because if I take the ITest interface and Test1 class verbatim (edit Oops - not exactly - because, as Nikolay says, it's failing for you because you're using ITest as the generic type parameter for the BindingList<T> which I don't here) from your code and write this test:
[TestClass]
public class UnitTest1
{
int counter = 0;
[TestMethod]
public void TestMethod1()
{
BindingList<Test1> list = new BindingList<Test1>();
list.RaiseListChangedEvents = true;
int evtCount = 0;
list.ListChanged += (object sender, ListChangedEventArgs e) =>
{
Console.WriteLine("Changed, type: {0}", e.ListChangedType);
++evtCount;
};
list.Add(new Test1() { Data = "yo yo" });
Assert.AreEqual(1, evtCount);
list[0].Data = "ya ya";
Assert.AreEqual(2, evtCount);
}
}
The test passes correctly - with evtCount ending up at 2, as it should be.
I found in constructor some interesting things:
public BindingList()
{
// ...
this.Initialize();
}
private void Initialize()
{
this.allowNew = this.ItemTypeHasDefaultConstructor;
if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) // yes! all you're right
{
this.raiseItemChangedEvents = true;
foreach (T local in base.Items)
{
this.HookPropertyChanged(local);
}
}
}
Quick fix 4 this behavior:
public class BindingListFixed<T> : BindingList<T>
{
[NonSerialized]
private readonly bool _fix;
public BindingListFixed()
{
_fix = !typeof (INotifyPropertyChanged).IsAssignableFrom(typeof (T));
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (RaiseListChangedEvents && _fix)
{
var c = item as INotifyPropertyChanged;
if (null!=c)
c.PropertyChanged += FixPropertyChanged;
}
}
protected override void RemoveItem(int index)
{
var item = base[index] as INotifyPropertyChanged;
base.RemoveItem(index);
if (RaiseListChangedEvents && _fix && null!=item)
{
item.PropertyChanged -= FixPropertyChanged;
}
}
void FixPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!RaiseListChangedEvents) return;
if (_itemTypeProperties == null)
{
_itemTypeProperties = TypeDescriptor.GetProperties(typeof(T));
}
var propDesc = _itemTypeProperties.Find(e.PropertyName, true);
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOf((T)sender), propDesc));
}
[NonSerialized]
private PropertyDescriptorCollection _itemTypeProperties;
}
Thanks for replies!
The type of elements that you parameterize BindingList<> with (ITest in your case) must be inherited from INotifyPropertyChanged. Options:
Change you inheritance tree ITest: INotifyPropertyChanged
Pass concrete class to the generic BindingList
I have the exact problem as this guy in the Silverlight Forum and the accepted answer is :
In this case, your property didn't actually change value. You added
something to your List, but the list is the same List so when the
DependencyProperty mechanism sees that the actual value (reference to
your List) didn't change, it didn't raise your OnChanged handler
This is a great explication but not an answer to fix this problem. I can find on Google many suggestion for WPF but not for Silverlight.
The problem is describe as this one : You have a DependencyProperty that is called when the variable is initialized but after then nothing is updated.
public partial class MyGrid : UserControl
{
public MyGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty ShapesProperty = DependencyProperty.Register(
"Shapes", typeof(ObservableCollection<ModelItem>), typeof(MyGrid), new PropertyMetadata(OnShapesPropertyChanged));
public ObservableCollection<ModelItem> Shapes
{
private get { return (ObservableCollection<ModelItem>)GetValue(ShapesProperty); }
set { SetValue(ShapesProperty, value); }
}
private static void OnShapesPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((MyGrid)o).OnShapesPropertyChanged(e); //Fire Only Once
}
private void OnShapesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
dg.ItemsSource = e.NewValue as ObservableCollection<ModelItem>;
}
}
//--------
public class ViewModel : INotifyPropertyChanged
{
public Model Model { get; set; }
public RelayCommand cmd;
public ObservableCollection<ModelItem> ModelItemCollection
{
get
{
return Model.ModelItem;
}
}
public ViewModel()
{
Model = new Model();
Model.PropertyChanged += Model_PropertyChanged;
}
void Model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.PropertyName);
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("ModelItemCollection"));
}
}
public ICommand AddCmd
{
get { return cmd ?? (cmd = new RelayCommand(a => Model.ModelItem.Add(new ModelItem {Name = "asd"}))); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
///----------------------
public class Model: INotifyPropertyChanged
{
public ObservableCollection<ModelItem> ModelItem { get; set; }
public Model()
{
ModelItem = new ObservableCollection<ModelItem>();
ModelItem.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ModelItem_CollectionChanged);
}
void ModelItem_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("ModelItem"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ModelItem
{
public String Name { get; set; }
}
Even with explicit call of PropertyChanged() nothing is updated.
What is the workaround to let know the DependencyProperty that the ObservableCollection has elements that have changed?
Pseudocode:
BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget();
Look here: forcing a WPF binding to 'refresh' ...
Try this, usually works :)