I observe an odd behaviour of WPF ItemsControls: If a set the ItemsSource to an object which implements INotifyCollectionChanged and after that set the ItemsSource to null, the CollectionView which was created to provide the data to the ItemsControl still listens to the CollectionChanged-event of the source object.
If now the source collection is changed through a different thread, the CollectionView throws an exception (without being attached to any control).
While I understand why this is happening, I’m really stuck resolving this situation.
Therefore the main question is, how can I destroy a CollectionView so that it does not listen any more to CollectionChanged-event. Or how can I disable it doing that / detaching the underlying collection.
Please note: The described behavior is not with ObservableCollection. The source object is an IEnumerable of T and implements INotifyCollectionChanged.
You're looking for the CollectionView.DetachFromSourceCollection() method:
var collectionView = CollectionViewSource.GetDefaultView(yourEnumerable) as CollectionView;
collectionView.DetachFromSourceCollection();
Update
It seems, that under .net 4.5 there is this desired functionality. See the answer of HighCore. For those not having 4.5 I leave my workaround here, maybe it helps someone:
class DetachableNotifyCollectionChangedWrapper : IEnumerable, INotifyCollectionChanged {
IEnumerable m_source;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public DetachableNotifyCollectionChangedWrapper(IEnumerable enumerable) {
if (null == enumerable) throw new ArgumentNullException("enumerable"); ;
m_source = enumerable;
var ncc = m_source as INotifyCollectionChanged;
if (null != ncc) ncc.CollectionChanged += SourceCollectionChanged;
}
void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if (null != CollectionChanged) CollectionChanged(this,e);
}
public IEnumerator GetEnumerator() {
return m_source.GetEnumerator();
}
public void Detach() {
var ncc = m_source as INotifyCollectionChanged;
if (null != ncc) ncc.CollectionChanged -= SourceCollectionChanged;
}
}
To use this, set the Wrapper as the ItemsSource of the ItemsControl. Before setting then the ItemsSource to null, call Detach on the wrapper to unregister the changed-event. Something as follows:
var wrapper = m_lstLog.ItemsSource as DetachableNotifyCollectionChangedWrapper;
if (null != wrapper) wrapper.Detach();
m_lstLog.ItemsSource = null;
The wrapper can also be used from within a ViewModel.
Related
I am planning to have a threadsafe observable collection, where in I run a task on the background and update the UI as and when result is obtained using the dispatcher.
I got a download from the internet which is ThreadSafe. But I have a small concern. I wish to do a silent update on the UI. Since the user is already working or selecting the bound collection, I would not want to disturb the user selected entry. In other words, I would like to add an entry similar to Microsoft Outlook, when a new mail arrives.
Is this possible, and are there any such examples.
Thanks
You can have derived class from ObservableCollection and override it OnCollectionChanged function and raise it handler when ever you require. follows are code.
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
private bool suspendCollectionChangeNotification;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!this.suspendCollectionChangeNotification)
{
NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;
if (eventHandler != null)
{
Delegate[] delegates = eventHandler.GetInvocationList();
// Walk through invocation list
bool isEventInvoked = false;
foreach (NotifyCollectionChangedEventHandler handler in delegates)
{
isEventInvoked = false;
if (handler.Target is DispatcherObject)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// This check is to make sure if the call on a different thread than the dispatcher.
if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
{
// Invoke handler in the target dispatcher's thread
// Intentionally called begin invoke because there is a problem with Dispatcher.Invoke that it hangs when
// called simultaneously
dispatcherObject.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, handler, this, e);
isEventInvoked = true;
}
}
if (!isEventInvoked)
{
handler(this, e);
}
}
}
}
}
I am using WPF with the currently latest and greatest version of Caliburn.Micro (1.4.1). I use IWindowManager.ShowWindow(...) to open an new modeless window:
private void OpenOrReactivateInfoView()
{
if(this.infoViewModel == null)
{
this.infoViewModel = new InfoViewModel();
}
this.windowManager.ShowWindow(this.infoViewModel);
}
Instead of opening a new window each time when OpenOrReactivateInfoView() is called, I would like to check whether the window ist still open and if it is, the existing window should just regain focus.
What would we be a good Calibrun.Micro-way to solve this? I sure would like to avoid keeping a reference to the window (or any UIElement for that matter) itself in the viewmodel. Also note that this is a common behavior for a lot of modeless dialogs, so it is preferred solve this in a generic reusable way.
Does Caliburn.Micro already have means for this built in?
The WindowManager source code always creates a new window, so what you really want to do is only use the WindowManager.ShowWindow method if you actually intend to create a new window.
The first thing you want to do is hold a persistent reference to your view model like this:
private readonly InfoViewModel infoViewModel = new InfoViewModel();
private void OpenOrReactivateInfoView()
{
this.windowManager.ShowWindow(this.infoViewModel);
}
Then, in your view model, create a method called Focus or whatever you want like this:
public void Focus()
{
var window = GetView() as Window;
if (window != null) window.Activate();
}
Then revisit your OpenOrReactivateInfoView() method make a slight adjustment like this:
private void OpenOrReactivateInfoView()
{
if (!this.infoViewModel.IsActive)
this.windowManager.ShowWindow(this.infoViewModel);
else
this.infoViewModel.Focus();
}
This method worked for me.
A fairly straightforward way to keep track of your windows without actually
having to implement IViewAware would be to keep a dictionary of weak references
to your ViewModels and Views that go together and then checking if you already
have an existing Window or not. Could be implemented either as a decorator to
the WindowManager, subclass or extension.
Something as simple as the following should do the trick assuming you don't
actually plan on opening enough windows that even the dead WeakReferences
would impact performance. If it is going to be long running it shouldn't be
that hard to implement some sort of cleanup.
public class MyFancyWindowManager : WindowManager
{
IDictionary<WeakReference, WeakReference> windows = new Dictionary<WeakReference, WeakReference>();
public override void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null)
{
NavigationWindow navWindow = null;
if (Application.Current != null && Application.Current.MainWindow != null)
{
navWindow = Application.Current.MainWindow as NavigationWindow;
}
if (navWindow != null)
{
var window = CreatePage(rootModel, context, settings);
navWindow.Navigate(window);
}
else
{
var window = GetExistingWindow(rootModel);
if (window == null)
{
window = CreateWindow(rootModel, false, context, settings);
windows.Add(new WeakReference(rootModel), new WeakReference(window));
window.Show();
}
else
{
window.Focus();
}
}
}
protected virtual Window GetExistingWindow(object model)
{
if(!windows.Any(d => d.Key.IsAlive && d.Key.Target == model))
return null;
var existingWindow = windows.Single(d => d.Key.Target == model).Value;
return existingWindow.IsAlive ? existingWindow.Target as Window : null;
}
}
I have come up with this extension method. It works but I am not particulary happy with it, it is still somewhat hackish.
It is clearly a designsmell that this extension has to make so many assumption about the model (do you see also those nasty exceptions?).
using System;
using System.Collections.Generic;
using Caliburn.Micro;
public static class WindowManagerExtensions
{
/// <summary>
/// Shows a non-modal window for the specified model or refocuses the exsiting window.
/// </summary>
/// <remarks>
/// If the model is already associated with a view and the view is a window that window will just be refocused
/// and the parameter <paramref name="settings"/> is ignored.
/// </remarks>
public static void FocusOrShowWindow(this IWindowManager windowManager,
object model,
object context = null,
IDictionary<string, object> settings = null)
{
var activate = model as IActivate;
if (activate == null)
{
throw new ArgumentException(
string.Format("An instance of type {0} is required", typeof (IActivate)), "model");
}
var viewAware = model as IViewAware;
if (viewAware == null)
{
throw new ArgumentException(
string.Format("An instance of type {0} is required", typeof (IViewAware)), "model");
}
if (!activate.IsActive)
{
windowManager.ShowWindow(model, context, settings);
return;
}
var view = viewAware.GetView(context);
if (view == null)
{
throw new InvalidOperationException("View aware that is active must have an attached view.");
}
var focus = view.GetType().GetMethod("Focus");
if (focus == null)
{
throw new InvalidOperationException("Attached view requires to have a Focus method");
}
focus.Invoke(view, null);
}
}
I have in my App class an object Order, both implementing INotfiyPropertyChanged.
When an Order is concluded, I set it to null and do a new Order() for restarting a new Order.
The problem is: it seems the objects that whose DataContext was bound to Order seems they are always linked to the older Order
What can I do to for not having to rebind again manually when I restart an Order?
For every object with this DataContext, I need to do object.DataContext= App.Order. What can I do to avoid this?
Some code:
public partial class App : Application, INotifyPropertyChanged
{
private Order m_order = new Order();
public Order Order
{
get { return m_order; }
set
{
m_order = value;
NotifyPropertyChanged("Order");
}
}
//...
public bool getOrderClosed()
{
if (Order != null)
{
Order = null;
}
return (Order == null);
}
public bool getOrderOpened()
{
if (Order == null)
Order = new Order();
return (Order != null);
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//code on the part where the order is finished
private void Confirm_Click(object sender, RoutedEventArgs e)
{
//...
if (SaveOrder())
{
theApp.getOrderClosed();
theApp.getOrderOpened();
theApp.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.Basket.DataContext = theApp.Order;
}
}
If I understand correctly, your Order object is the DataContext. If you're setting the DataContext in codebehind, it'd looks something like:
[some_element].DataContext = myApp.Order;
What you'd need to do is bind the DataContext to myApp.Order. This way, when you do something like
myApp.Order = new Order(...);
the DataContext for [some_element] will change as well. If you post your XAML code where you're using the Order object as the DataContext, I can show you exactly what your binding on the DataContext should look like.
With what you've done, only the changes within the DataContext will be picked up; when you change the property that you're using as the DataContext changes, the DataContext itself does not.
I had a similar problem a few weeks ago. I found that when assigning a bound property to a new object the binding was lost, so as a workaround I had to create a new temp object and then copy all the fields into my bound property, that way you effectivly have a fresh object and the binding is maintained.
Hope this helps!
Maybe change the binding to UpdateSourceTrigger=PropertyChanged?
If App implements INotifyPropertyChanged properly, it will fire when you set App.Order to a new instance, and the binding will update.
Are you sure it is implemented properly?
eg.
class App
{
public Order Order
{
get
{
return _order;
}
set
{
if (value != _order)
{
_order = value;
FirePropertyChanged("Order");
}
}
}
}
Image you are creating a custom control behaving like ComboBox in WPF.
As a source of items you provide IQueryable<T> (or any kind of IEnumerable collection),
but you don't want to allow the control to call GetIterator() and iterate through it (some kind of a lazy loading).
Let's say you inherit from the (because you want all the funcionality of that control)
System.Windows.Controls.Primitives.Selector
class.
The Selector class inherits from System.Windows.Controls.ItemsControl class which provides a the well known dependency property ItemsSource.
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ItemsControl),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ItemsControl.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsControl control = (ItemsControl) d;
IEnumerable oldValue = (IEnumerable) e.OldValue;
IEnumerable newValue = (IEnumerable) e.NewValue;
ItemValueStorageField.ClearValue(d);
if ((e.NewValue == null) && !BindingOperations.IsDataBound(d, ItemsSourceProperty))
{
control.Items.ClearItemsSource();
}
else
{
control.Items.SetItemsSource(newValue); // PROBLEM
}
control.OnItemsSourceChanged(oldValue, newValue);
}
If I see it correctly, this is the place where it iterates.
internal void SetItemsSource(IEnumerable value)
{
if ((!this.IsUsingItemsSource && (this._internalView != null)) && (this._internalView.RawCount > 0))
{
throw new InvalidOperationException(SR.Get("CannotUseItemsSource"));
}
this._itemsSource = value;
this._isUsingItemsSource = true;
this.SetCollectionView(CollectionViewSource.GetDefaultCollectionView(this._itemsSource, this.ModelParent));
}
So I've decided to override metadata of ItemsSourceProperty and point it to my own static method,
where I'm planing not co call SetItemsSource (rather delay it).
How should it be done in your opinion?
Thank you
Your best bet would be to probably add a new dependency property, say DelayedItemsSource of type IEnumerable. Then you could bind ItemsSource to DelayedItemsSource after your delay.
Ok... this has me stumped. I've overridden OnContentTemplateChanged in my UserControl subclass. I'm checking that the value passed in for newContentTemplate does in fact equal this.ContentTemplate (it does) yet when I call this...
var textBox = this.ContentTemplate.FindName("EditTextBox", this);
...it throws the following exception...
"This operation is valid only on elements that have this template applied."
Per a commenter in another related question, he said you're supposed to pass in the content presenter for the control, not the control itself, so I then tried this...
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp);
...where FindVisualChild is just a helper function used in MSDN's example (see below) to find the associated content presenter. While cp is found, it too throws the same error. I'm stumped!!
Here's the helper function for reference...
private TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
where TChildItem : DependencyObject {
for(int i = 0 ; i < VisualTreeHelper.GetChildrenCount(obj) ; i++) {
var child = VisualTreeHelper.GetChild(obj, i);
if(child is TChildItem typedChild) {
return typedChild;
}
else {
var childOfChild = FindVisualChild<TChildItem>(child);
if(childOfChild != null)
return childOfChild;
}
}
return null;
}
Explicitly applying the template before calling the FindName method will prevent this error.
this.ApplyTemplate();
As John pointed out, the OnContentTemplateChanged is being fired before it is actually applied to the underlying ContentPresenter. So you'd need to delay your call to FindName until it is applied. Something like:
protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
this.Dispatcher.BeginInvoke((Action)(() => {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in OnContentTemplateChanged";
}), DispatcherPriority.DataBind);
}
Alternatively, you may be able to attach a handler to the LayoutUpdated event of the UserControl, but this may fire more often than you want. This would also handle the cases of implicit DataTemplates though.
Something like this:
public UserControl1() {
InitializeComponent();
this.LayoutUpdated += new EventHandler(UserControl1_LayoutUpdated);
}
void UserControl1_LayoutUpdated(object sender, EventArgs e) {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in UserControl1_LayoutUpdated";
}
The ContentTemplate isn't applied to the ContentPresenter until after that event. While the ContentTemplate property is set on the control at that point, it hasn't been pushed down to bindings internal to the ControlTemplate, like the ContentPresenter's ContentTemplate.
What are you ultimately trying to do with the ContentTemplate? There might be a better overall approach to reach your end goal.