Data binding doesn't work on Custom controls inside collection - wpf

WPF Data binding doesnt work for custom controls that are defined inside a xaml collection tag. I just want to define a collection of custom widgets inside a custom control and bind some widgets properties against viewmodel properties. Like so.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget ImportantToggle="{Binding SomeToggle}"/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</Grid>
</Window>
That is my custom control. I use an obseravblecollection for the widgets and call SetValue in the constructor to get propertychanged callback later (right now not used in example)
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
namespace WpfApp1
{
public class MyCustomControl : FrameworkElement
{
public ObservableCollection<MyCustomWidget> Widgets
{
get { return (ObservableCollection<MyCustomWidget>)this.GetValue(WidgetsProperty); }
set { this.SetValue(WidgetsProperty, value); }
}
public static DependencyProperty WidgetsProperty = DependencyProperty.Register("Widgets", typeof(ObservableCollection<MyCustomWidget>), typeof(MyCustomControl), new PropertyMetadata(null, (e, args) => ((MyCustomControl)e).WidgetsChanged(args)));
public void WidgetsChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("widgets collection object changed inside my custom control!");
}
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
}
}
}
and that is my custom widget:
namespace WpfApp1
{
public class MyCustomWidget : FrameworkContentElement
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty = DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget), new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
}
And finally my simplistic ViewModel:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp1
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _someToggle;
public bool SomeToggle
{
get { return this._someToggle; }
set
{
this._someToggle = value;
this.NotifyPropertyChanged();
}
}
public MainViewModel()
{
this.SomeToggle = !this.SomeToggle;
}
}
}
Thats the output I get from Debug.Writeline: widgets collection object changed inside my custom control!
Observation: I cant bind against properties of MyCustomWidget. I understand that the binding might fail in this scenario because the observablecollection is created inside of the constructor of mycustomcontrol, but I dont know how to fix it to get the binding working inside mycustomwidget.

For that binding to work, your local:MyCustomWidget needs to have the same DataContext as the main window. WPF elements inherit their logical parent's DataContext. MyCustomWidget doesn't, because it's not in the logical tree. It's just sitting there. You're not adding it to any kind of normal child collection of its parent, just to a random ObservableCollection that the framework doesn't know about.
The code below is probably a crude hack. I haven't investigated this corner of WPF. I urge you with the utmost sincerity to find out the right way of doing this. But with this addition to your code, I hit the propertychanged event in MyCustomWidget when the binding is initialized.
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
Widgets.CollectionChanged += Widgets_CollectionChanged;
}
private void Widgets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems is System.Collections.IEnumerable)
{
foreach (MyCustomWidget widget in e.NewItems)
{
AddLogicalChild(widget);
}
}
}
By the way, you can save the trouble of toggling the toggle in the MainViewModel constructor. That happens long before the binding exists. I added a checkbox instead:
<StackPanel>
<CheckBox IsChecked="{Binding SomeToggle}">Test Toggle</CheckBox>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</StackPanel>
Update:
This omits your Widgets collection entirely, and the binding works without any effort on our part. The child widgets will be in MyCustomControl.Children. Importantly that we aren't limiting the child type to MyCustomWidget any more. That's a significant design change, and may not fit your requirements. You could examine the Panel class closely, and write a class that works the same way, but accepts only one type of child (that would mean writing an analog of UIElementCollection, which will be mostly a big pile of tedious boilerplate).
MyCustomControl.cs
[ContentProperty("Children")]
public class MyCustomControl : Panel
{
}
MyCustomWidget.cs
public class MyCustomWidget : Control
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty =
DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget),
new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
MainWindow.xaml
<local:MyCustomControl>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl>

Related

Closing Child Window from ViewModel

I have Cancel Command in ViewModel.
Which is bound to a cannel Button in child View.
When i press cancel button it will clear all my unsaved data in viewModel.
Additionally i have to close the current instance of child window.-This is where I am stuck.
I am using MVVM.
I use the following pattern.
I have a base class for my ViewModel
public abstract class ClosableViewModel : IClosableViewModel
{
public event EventHandler Close;
protected virtual void CloseView()
{
var handler = Close;
if (handler != null) handler(this, EventArgs.Empty);
}
}
which is implementing this interface
public interface IClosableViewModel
{
event EventHandler Close;
}
And a window base class for my View I want to show and close via a ViewModel
public class ClosableWindow : Window
{
public ClosableWindow(IClosableViewModel viewModel)
{
DataContext = viewModel;
viewModel.Close += (s, e) => Close();
}
}
Your ViewModel which is the DataContext from your View you want to show as dialog has to inherit from ClosableViewModel and your dialog has to inherit from ClosableWindow. When you want to close your View from the ViewModel you just have to call the CloseView method.
An alternative to using an event is an attached property that goes on the view. The property changed handler will find the parent window of the view and close it as soon as a particular value is recognized.
using System.Windows;
namespace WpfApp1
{
public class CloseSignal
{
public static readonly DependencyProperty SignalProperty =
DependencyProperty.RegisterAttached("Signal", typeof(bool), typeof(CloseSignal),
new PropertyMetadata(OnSignalChanged));
public static bool GetSignal(DependencyObject dp)
{
return (bool)dp.GetValue(SignalProperty);
}
public static void SetSignal(DependencyObject dp, bool value)
{
dp.SetValue(SignalProperty, value);
}
private static void OnSignalChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
if (!(bool)e.NewValue)
return;
Window parent = Window.GetWindow(dp);
if (parent != null)
parent.Close();
}
}
}
And the view's XAML looks something like...
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="350" Width="525"
local:CloseSignal.Signal="{Binding Signal}">
...
</Window>

Can you have an event setter bound to an event of the data context?

Im trying to create something like this -
I have an observable collection of points. Each point has a position and a colour. When any points position or colour changes(they implement notification change), I want to "repaint" the background gradient. Currently I have an itemscontrol where I have the sliders bound to the points position and the gradient is initially drawn. Now, I want to know how I can call a function in the code behind of my view when the propertychanged event on a 'point' fires, so that I can repaint the gradient. Im wondering if an event setter can somehow be used?
Whilst I could do the propertychanged event subscribing in code behind, I'd like to do it in XAML?
PLease note : I specifically want to take this approach of manually repainting in code behind for other reasons, so if I could get answers to the specific problem above rather than alternative solutions please.
I guess you can create an attached property to subscribe to PropertyChanged events of the value of the DataContext property.
public static class Props
{
public static DependencyProperty OnPropertyChangedProperty = DependencyProperty.RegisterAttached(
"OnPropertyChanged", typeof(PropertyChangedEventHandler), typeof(Props),
new PropertyMetadata(OnPropertyChangedPropertyChanged));
public static PropertyChangedEventHandler GetOnPropertyChanged (DependencyObject d)
{
return (PropertyChangedEventHandler)d.GetValue(OnPropertyChangedProperty);
}
public static void SetOnPropertyChanged (DependencyObject d, PropertyChangedEventHandler value)
{
d.SetValue(OnPropertyChangedProperty, value);
}
private static void OnPropertyChangedPropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var inpc = (INotifyPropertyChanged)((FrameworkElement)d).DataContext;
if (inpc == null)
throw new ArgumentException("DataContext of the framework element must not be null.");
var oldChanged = (PropertyChangedEventHandler)e.OldValue;
if (oldChanged != null)
inpc.PropertyChanged -= oldChanged;
var newChanged = (PropertyChangedEventHandler)e.NewValue;
if (newChanged != null)
inpc.PropertyChanged += newChanged;
}
}
Usage:
<Window x:Class="So17382721PropertyChangedXaml.MainWindow" x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:So17382721PropertyChangedXaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Foo}">
<!-- Here, we subscribe to DataContext.PropertyChanged;
handler is defined in the MainWindow class -->
<Grid local:Props.OnPropertyChanged="{Binding FooPropertyChanged, ElementName=root}">
<TextBox Text="{Binding Bar, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Foos, ElementName=root}"/>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace So17382721PropertyChangedXaml
{
public partial class MainWindow
{
public ObservableCollection<Foo> Foos { get; private set; }
public MainWindow ()
{
Foos = new ObservableCollection<Foo> {
new Foo { Bar = "1" },
new Foo { Bar = "2" },
new Foo { Bar = "3" },
};
InitializeComponent();
}
private void OnFooPropertyChanged (object sender, PropertyChangedEventArgs e)
{
MessageBox.Show(this, string.Format("{0} of {1} changed.", e.PropertyName, sender));
}
// Subscribing to non-RoutedEvents in XAML is not straightforward, but we can define a property
public PropertyChangedEventHandler FooPropertyChanged
{
get { return OnFooPropertyChanged; }
}
}
public class Foo : INotifyPropertyChanged
{
private string _bar;
public string Bar
{
get { return _bar; }
set
{
_bar = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Note: the attached property Props.OnPropertyChanged expects that DataContext is not changed during lifetime and is already specified. Handling DataContextChanged events is left as an exircize, if you need it.

IndexOutOfRangeException when changing selected TabItem twice

I have the following simple WPF-app:
<Window x:Class="TabControlOutOfRangeException.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding ItemsSource}"
SelectedIndex="{Binding SelectedIndex, IsAsync=True}" />
</Window>
with following simple code-behind:
using System.Collections.Generic;
namespace TabControlOutOfRangeException
{
public partial class MainWindow
{
public List<string> ItemsSource { get; private set; }
public int SelectedIndex { get; set; }
public MainWindow()
{
InitializeComponent();
ItemsSource = new List<string>{"Foo", "Bar", "FooBar"};
DataContext = this;
}
}
}
When I click on the second tab ("Bar"), nothing is displayed. When I click again on any tab, I get an IndexOutOfRangeException. Setting IsAsync to False, the TabControl works.
Unfortunately, I have the requirement to query the user a "Save changes?" question when he leaves the current tab. So I wanted to set the SelectedIndex back to the old value within the set-property. Obviously this doesn't work. What am I doing wrong?
Update
I've subclassed the TabControl with the evil hack and it works for me. Here is the code of MainWindow.xaml:
<Window x:Class="TabControlOutOfRangeException.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TabControlOutOfRangeException="clr-namespace:TabControlOutOfRangeException" Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControlOutOfRangeException:PreventChangingTabsTabControl
ItemsSource="{Binding ItemsSource}"
SelectedIndex="{Binding SelectedIndex}"
CanChangeTab="{Binding CanChangeTab}" Margin="0,0,0,51" />
<CheckBox Content="CanChangeTab" IsChecked="{Binding CanChangeTab}" Margin="0,287,0,0" />
</Grid>
</Window>
And here MainWindow.xaml.cs:
using System.Collections.Generic;
using System.ComponentModel;
namespace TabControlOutOfRangeException
{
public partial class MainWindow : INotifyPropertyChanged
{
public int SelectedIndex { get; set; }
public List<string> ItemsSource { get; private set; }
public MainWindow()
{
InitializeComponent();
ItemsSource = new List<string> { "Foo", "Bar", "FooBar" };
DataContext = this;
}
private bool _canChangeTab;
public bool CanChangeTab
{
get { return _canChangeTab; }
set
{
_canChangeTab = value;
OnPropertyChanged("CanChangeTab");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
}
}
And finally the subclassed TabControl:
using System;
using System.Windows;
using System.Windows.Controls;
namespace TabControlOutOfRangeException
{
public class PreventChangingTabsTabControl : TabControl
{
private int _previousTab;
public PreventChangingTabsTabControl()
{
SelectionChanged += (s, e) =>
{
if (!CanChangeTab)
{
e.Handled = true;
SelectedIndex = _previousTab;
}
else
_previousTab = SelectedIndex;
};
}
public static readonly DependencyProperty CanChangeTabProperty = DependencyProperty.Register(
"CanChangeTab",
typeof(Boolean),
typeof(PreventChangingTabsTabControl)
);
public bool CanChangeTab
{
get { return (bool)GetValue(CanChangeTabProperty); }
set { SetValue(CanChangeTabProperty, value); }
}
}
}
I'd consider a redesign of that window instead of introducing a heap of new problems by just trial-and-erroring on the "IsAsync" property of the binding.
I am not sure if a tab control will allow this level of control you seek. You could try to catch the event when someone tries to change the selected item, but you would not be able to cancel it out. There is a way however, see Option 4 if you dont want to read the other suggestions.
Option 1: The custom control
I would consider writing a bit of custom code that mimics the functionality of an item container. Its easy to achieve your desired behaviour this way. Just bind a command to the buttons (or whatever control you wish the user to click on), and return CanExecute with false if there are still changes to be submitted - or ask your user whatever you want when it gets executed, and only change the content displayed (ie your custom "TabItem") if desired.
Option 2: Preventing the user by disabling the tabs
Another way would be to bind the "IsEnabled" property of each of the tabitems to a dependency property on your viewmodel, that controls which of them is available to the user. Like, you know that the first page still needs work, just disable all the other ones meanwhile. But be aware that right now you are not creating any TabItems - your content are just plain strings.
public List<TabItem> ItemsSource { get; private set; }
....
ItemsSource = new List<TabItem> { new TabItem() { Header = "Foo", Content = "Foo" }, new TabItem() { Header = "Bar", Content = "Bar" }, new TabItem() { Header = "FooBar", Content = "FooBar" } };
Since you don't want to prevent the user doing something but rather would like to ask to save the changes, i'd go for the custom control route. Still there is option 3.
Option 3: Popup window
Use a popup window and ask to save changes if the user is finished with changing whatever is on that page and clicks on the "Close" button (rather than the "Save" button that should also reside on the same page ;) )
Option 4: Check on StackOverflow
Actually i did that for you, and here is a solution another user has found for the exact same problem: WPF Tab Control Prevent Tab Change
The reason why i didnt post that up-front was that i personally wouldnt do it that way because, man do i HATE applications that do this.
Here you go.
Try actually implementing the SelectedIndex
namespace TabControlOutOfRangeException
{
public partial class MainWindow
{
public List<string> ItemsSource { get; private set; }
private int selectedIndex
public int SelectedIndex {
get { return selectedIndex; }
set { selecectedIndex = value; } }
public MainWindow()
{
InitializeComponent();
ItemsSource = new List<string>{"Foo", "Bar", "FooBar"};
DataContext = this;
}
}
}
If you want to be able to affect the TabControl the binding needs to be two-way, i.e. your code-behind needs to be able to notify the view that the property changed, for that you should implement INotifyPropertyChanged in your window, e.g.
public partial class MainWindow : INotifyPropertyChanged
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex != value)
{
_selectedIndex = value;
OnPropertyChanged("SelectedIndex");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Async bindings are usually for properties which have a long-running getter, with e.g. a database query, you should not need this here.
In case you want to to change the selectedIndex in the setter itself, then to get it updated on UI, you have to raise the property changed in an async manner like this -
public partial class MainWindow : INotifyPropertyChanged
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex != value)
{
_selectedIndex = value;
OnPropertyChangedAsAsync("SelectedIndex");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected virtual void OnPropertyChangedAsAsync(string propertyName)
{
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate { OnPropertyChanged(propertyName); }, DispatcherPriority.Render, null);
}

Binding to WPF ViewModel properties

I'm just playing around with WPF and MVVM, and I have made a simple app that displays a Rectangle that changes color whenever Network availability changes.
But when that happens, I get this error: Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.
Code
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="600">
<DockPanel LastChildFill="True">
<Rectangle x:Name="networkStatusRectangle" Width="200" Height="200" Fill="{Binding NetworkStatusColor}" />
</DockPanel>
</Window>
Code-behind
using System.Windows;
using WpfApplication1.ViewModels;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new NetworkViewModel();
}
}
}
ViewModel
using System.ComponentModel;
using System.Net.NetworkInformation;
using System.Windows.Media;
namespace WpfApplication1.ViewModels
{
public class NetworkViewModel : INotifyPropertyChanged
{
private Brush _NetworkStatusColor;
public Brush NetworkStatusColor
{
get { return _NetworkStatusColor; }
set
{
_NetworkStatusColor = value;
NotifyOfPropertyChange("NetworkStatusColor");
}
}
public NetworkViewModel()
{
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
}
protected void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
if (e.IsAvailable)
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Green);
}
else
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Red);
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void NotifyOfPropertyChange(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I assume that I should change the NetworkStatusColor property by invoking something?
You assume correctly. It's the Dispatcher class and the .Invoke method you want to take a look at.
Something a bit like this:
if (this.Dispatcher.Thread != Thread.CurrentThread)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(...your method...), any, params, here);
return
}
There's an MSDN article here with some more info.
With MVVM you have a couple of options when dealing with dispatching. Either you can send some kind of message to your view to have it invoke the operation for you, or you can create some kind of abstract dispatcher service that you are able to easily mock.
Take a look at the MVVM Light toolkit, as it includes a simple dispatcher-service you can use/copy.

INotifyPropertyChanged not working on ObservableCollection property

I have a class called IssuesView which implements INotifyPropertyChanged. This class holds an ObservableCollection<Issue> and exposes it as a DependencyProperty called Issues for consumption by Bindings. It is defined as below -
public class IssuesView : DependencyObject, INotifyPropertyChanged
{
public Issues Issues
{
get { return (Issues)GetValue(IssuesProperty); }
set
{
SetValue(IssuesProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Issues"));
}
}
}
// Using a DependencyProperty as the backing store for Issues. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IssuesProperty =
DependencyProperty.Register("Issues", typeof(Issues), typeof(IssuesView), new UIPropertyMetadata(null));
public IssuesView()
{
Refresh();
}
public void Refresh()
{
this.Issues = new Issues();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I have a test page declared like this -
<Page x:Class="Tracker.Pages.DEMO"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cont="clr-namespace:Tracker.Controls"
Title="DEMO">
<StackPanel>
<Button Click="Button_Click">Change</Button>
<cont:IssueTimeline IssuesForTimeline="{Binding Source={StaticResource issuesView},Path = Issues}"/>
</StackPanel>
The IssuesView class is defined in Application.Resources.
Now in the event hadnler for the button i have this code -
private void Button_Click(object sender, RoutedEventArgs e)
{
IssuesView iv = Application.Current.FindResource("issuesView") as IssuesView;
if (!once)
{
foreach (Issue i in iv.Issues)
{
i.DormantFor = new TimeSpan(30, 0, 0, 0);
i.AssignedUserID = 12;
i.Name = "MyName";
i.Priority = Issue.Priorities.Critical;
i.Status = Issue.Statuses.New;
i.Summary = "NewSummary";
}
once = true;
}
else
{
iv.Refresh();
}
once is a simple boolean to test mutation of the collection versus repopulation.
The first button click alters the collection's items and the UI is updated properly since the items implement INotifyPropertyChanged but the second click repopulates the collection but does not update the UI even though the event is not null and fires properly.
Why does the UI not update on the second click? How can i make it so that repopulating the collection will cause a UI update?
You really need to simplify your repro. I can see several things wrong with it, but cannot help to solve your problem without seeing all of it. Here is my simple repro, which works just fine.
Window1.xaml:
<Window x:Name="_root" x:Class="CollectionRepro.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel DataContext="{Binding ElementName=_root}">
<ItemsControl ItemsSource="{Binding Issues}"/>
<Button x:Name="_addButton">Add</Button>
<Button x:Name="_resetButton">Reset</Button>
</StackPanel>
</Window>
Window1.xaml.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace CollectionRepro
{
public partial class Window1 : Window
{
public static readonly DependencyProperty IssuesProperty = DependencyProperty.Register("Issues",
typeof(ICollection<string>),
typeof(Window1));
public ICollection<string> Issues
{
get { return (ICollection<string>)GetValue(IssuesProperty); }
set { SetValue(IssuesProperty, value); }
}
public Window1()
{
InitializeComponent();
Reset();
_addButton.Click +=new RoutedEventHandler(_addButton_Click);
_resetButton.Click += new RoutedEventHandler(_resetButton_Click);
}
void _resetButton_Click(object sender, RoutedEventArgs e)
{
Reset();
}
void _addButton_Click(object sender, RoutedEventArgs e)
{
Issues.Add("Another issue");
}
private void Reset()
{
Issues = new ObservableCollection<string>();
}
}
}
First: there's no reason to implement INotifyProperty changed for DependencyProperties. DependencyProperties know when they change.
Second: I don't see an ObservableCollection in your code.
Third: it's not entirely clear to me (from the code you posted) where the issues you modify in the first click come from. I assume from another action, not posted here.
Am I correct if I assume that you want to clear the issues list with the second click (since I don't know what the Issues constructor does)?

Resources