View does not manage to observe all property changes in ViewModel - wpf

Case:
There is a View which contains CustomRichTextBox.
public static readonly DependencyProperty NewOutputLogItemProperty = DependencyProperty.Register("NewOutputLogItem",
typeof(OutputLog ), typeof(CustomRichTextBox), new FrameworkPropertyMetadata(null, OnNewOutputLogAdded));
public OutputLog NewOutputLogItem
{
get { return (OutputLog )GetValue(NewOutputLogItemProperty ); }
set { SetValue(NewOutputLogItemProperty , value); }
}
private static void OnNewOutputLogAdded(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var richBox = (CustomRichTextBox)obj;
var outputLog = (OutputLog )args.NewValue;
richBox.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
var mainParagraph = richBox.Document.Blocks.FirstBlock as Paragraph;
if (mainParagraph != null)
{
mainParagraph.Inlines.AddRange(outputLog.GetInlines());
}
}));
}
There is also ViewModel which via MVVM light Messenger receives messages and set that property View binds to.
public void ProcessNotificationMessage(OutputLog message)
{
NewOutputLog = message;
}
Unfortunately I observed that some messages are not depicted if ProcessNotificationMessage works too fast and as a result View misses single messages.
Do you have any idea how to ensure it to work properly?

Related

Dependency Property design data

I have a UserControl that has an ObservableCollection dependency property with a property changed callback. The callback rebuilds the nodes in a TreeView control. This all works fine but I would like to be able to have design data. Unfortunately, neither the constructor, the callback nor a default value function call is called by the designer unless I embed my control in another. Is there a way loading default data in this scenario?
Below is the code behind for my control
public partial class ScheduleResourcesSummaryTreeView : UserControl
{
public ObservableCollection<PerformanceProjection> SelectedPerformances
{
get { return (ObservableCollection<PerformanceProjection>)GetValue(SelectedPerformancesProperty); }
set { SetValue(SelectedPerformancesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPerformancesProperty =
DependencyProperty.Register("SelectedPerformances",
typeof(ObservableCollection<PerformanceProjection>),
typeof(ScheduleResourcesSummaryTreeView),
new FrameworkPropertyMetadata(
new ObservableCollection<PerformanceProjection>(),
FrameworkPropertyMetadataOptions.AffectsRender, SelectedPerformancesChanged)
);
private static void SelectedPerformancesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ScheduleResourcesSummaryTreeView resourcesTree)) return;
resourcesTree.ResourcesTree.Items.Clear();
var sessions = e.NewValue as ObservableCollection<PerformanceProjection> ?? new ObservableCollection<PerformanceProjection>();
if (sessions.Count == 0) return;
var projections = sessions.SelectMany(x => x.ResourceBookingProjections);
TreeNode<ResourceBookingProjection> resourceBookingTreeRoot = new RvtSummaryTree(new ObservableCollection<ResourceBookingProjection>(projections), "");
foreach (var treeNode in resourceBookingTreeRoot.Children)
{
resourcesTree.ResourcesTree.Items.Add((TreeNode<ResourceBookingProjection>)treeNode);
}
}
private static ObservableCollection<PerformanceProjection> DefaultCollection()
{
var prop = DesignerProperties.IsInDesignModeProperty;
var designMode = (bool) DependencyPropertyDescriptor
.FromProperty(prop, typeof(FrameworkElement))
.Metadata.DefaultValue;
if (!designMode)
return new ObservableCollection<PerformanceProjection>();
var designdata = new PerformanceProjectionsViewModelDesignData();
return designdata.SelectedPerformances;
}
public ScheduleResourcesSummaryTreeView()
{
InitializeComponent();
}
}

Catel and setting usercontrol's DataContext

I've tried to find a solution myself but I was not able, for some reason the DataContext is not correctly set up in the usercontrol's viewmodel
The idea is to have a single usercontrol that permits to perform a query on a fixed collection and allows the user to drop a treeviewitem that holds an item of the collection (in case the user have the treeview open)
In my main view I've defined :
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding PortfolioCompleteBox}" Height="25" LoadDefaultValue="True" />
Where PortfolioCompleteBox is a ViewModel defined in the MainViewModel as
public PortfolioChooserViewModel PortfolioCompleteBox
{
get { return GetValue<PortfolioChooserViewModel>(PortfolioChooserViewModelProperty); }
set { SetValue(PortfolioChooserViewModelProperty, value); }
}
public static readonly PropertyData PortfolioChooserViewModelProperty = RegisterProperty("PortfolioCompleteBox", typeof(PortfolioChooserViewModel));
public MainViewModel(ICreditLimitRepository creditLimitRepository, IDynamicContainer dynamicContainer)
{
this.creditLimitRepository = creditLimitRepository;
this.dynamicContainer = dynamicContainer;
LoadCreditLimitsCommand = new Command<object>(OnLoadCreditLimitsExecute, (() => OnLoadCreditLimitsCanExecute));
var viewModelFactory = this.GetServiceLocator().ResolveType<IViewModelFactory>();
PortfolioCompleteBox = viewModelFactory.CreateViewModel<PortfolioChooserViewModel>(null);
Model = new FiltersLoadModel();
}
My problem is that on the PortFolioChooserView I've the DataContext set to null (and I got 2 calls to the PortFolioChooserViewModel, one from the MainViewModel and the other one from the PortFolioChooserView's viewmodel locator)
public partial class PortfolioChooserView
{
private PortfolioChooserViewModel viewModel;
readonly bool isFirstLoad = true;
/// <summary>
/// Initializes a new instance of the <see cref="PortfolioChooserView"/> class.
/// </summary>
///
public PortfolioChooserView()
{
InitializeComponent();
if (isFirstLoad)
{
PortfolioCompleteBox.AllowDrop = true;
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
isFirstLoad = false;
this.Loaded += PortfolioChooserView_Loaded;
this.DataContextChanged += PortfolioChooserView_DataContextChanged;
}
}
void PortfolioChooserView_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
int t = 0;
}
void PortfolioChooserView_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
viewModel = (PortfolioChooserViewModel)this.DataContext;
}
private void OnElementDragOver(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var options = Telerik.Windows.DragDrop.DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions;
if (options != null)
{
var visual = options.DragVisual as TreeViewDragVisual;
if (visual != null) visual.IsDropPossible = true;
}
e.Handled = true;
}
private void OnElementDrop(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var context = ((IPortfolioAutoComplete)this.DataContext);
context.SetPortfolioAutoCompleteBox(e);
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
}
What am I doing wrong?
Thanks
Don't try to manage your own vm's
Catel will automatically accept a parent-vm as it's own vm as long as they are compatible. You don't need to handle this manually in your view loading in the view.
Instead of creating a VM in the parent VM, use a model only (so the vm only cares about what the VM itself should do). Then set the DC of the PortfolioChooserView to the model. Then the vm of the child view can accept the model in the ctor and be managed on it's own.
There are much better ways to communicate between vm's then trying to micro-manage like you are doing now. As always, see the docs.

WPF DependencyProperty chain notification

im exploring WPF world, i find a great example on the web about how to use binding on xml
http://www.codeproject.com/Articles/37854/How-to-Perform-WPF-Data-Binding-Using-LINQ-to-XML
Now im trying to extends this example: i want to create a "class in the middle" between the XElement and the UI and bind all togheder in a chain so, if i have a modification into the xml, then i have the property in the middle class updated then the UI updated too.
Here some code:
This is the class that wrap the XElement
public class XElementDataProvider : ObjectDataProvider
{
public XElementDataProvider()
{
ObjectInstance = XElement.Load(#"C:\MyFile.xml");
}
private static XElementDataProvider instance;
public static XElementDataProvider Instance
{
get
{
if (instance == null)
{
instance = new XElementDataProvider();
}
return instance;
}
}
}
This is the MiddleClass
public class MiddleClass : DependencyObject
{
XElementDataProvider xElementDataProvider;
XElement myxml;
public MiddleClass()
{
//here i get my dataprovider
xElementDataProvider = XElementDataProvider.Instance;
myxml = xElementDataProvider.Data as XElement;
//i bind my internal collection to the Elements...
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default//here i cant use TwoWay, give me //back an exeption
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
//just to have confirmation of the adding
myxml.Changed += new EventHandler<XObjectChangeEventArgs (myxml_Changed);
}
void myxml_Changed(object sender, XObjectChangeEventArgs e)
{
}
//i use a DependencyProperty to have also a change callback
public static readonly DependencyProperty XBookListProperty =
DependencyProperty.Register("XBookList", typeof(IEnumerable),
typeof(MiddleClass),
new PropertyMetadata(XBookPropertyChanged)
);
//here i have a notification only at start but no when i add a new book
private static void XBookPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MiddleClass middleClass = d as MiddleClass;
middleClass.XBookPropertyChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void XBookPropertyChanged(IEnumerable old, IEnumerable newValue)
{
}
//this is the propery i finally want to expose to the UI but im not able //to keep updated
public List<Book> bookList;
public List<Book> BookList
{
get
{
return bookList;
}
set
{
bookList = value;
}
}
//this is my internal list binded to the xml
private IEnumerable XBookList
{
get
{
return (IEnumerable)GetValue(XBookListProperty);
}
set
{
SetValue(XBookListProperty, value);
}
}
//here i try to add a book addind direcly to the xml//i expect a //notification of propery changed...but nothing
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
return true;
}
Book is a class thar repersents a book, let say it has only a name propery for now.
The UI class misses but it should bind on public List<Book> BookList and show books names to the user in a ListBox
Enyone knows why i dont recive any notification...or what i have to do to keep the public List<Book> BookList synchronized with private IEnumerable<XBookList>?
OK, after many attempts, the only solution I found is this one:
to have notifications when something changes in the IEnumerable<XBookList> you need to clear it ad rebind after you modify it.
In this way you have a first, not used notification, about the clear and then another notification about the new set.
Then in the handler you can synchronize the new list with the old one.
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
ClearValue(XBookListProperty);
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
return true;
}

WPF: Handle a large number of routed events

I need your recomandations for the following problem:
Let say you have a MyView type (UserControl), which defines a routed event IsSelectedChanged. It is raised every time myView.IsSelected property value is changed.
Also, you have a MyContainer (Canvas), which contains a very (very!) large number of children of type MyView. MyContainer has routed event MyViewsSelectionChanged, that is raised whenever MyViewsSelection is changed. MyViewsSelection is a set of MyView objects that have IsSelected property set to true. MyContainer will handle MyView.IsSelectedChanged for every child and will provide its MyViewSelection status to the MyContainerParent (Panel)
MyContainerParent will handle myContainer.MyViewsSelectionChanged event
The issue I am afraid of is that my application will under-perform for a very large selection of MyView objects, resulting in a sort of 'wildfire' of events.
Any recomandations to prevent the issue, will be much appreciated!
Thanks
some code:
BatchView.IsSelectedChanged (MyView):
public static readonly RoutedEvent IsSelectedChangedEvent = EventManager.RegisterRoutedEvent(
"IsSelectedChanged",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(BatchView)
);
/// <summary>
/// Occurs when IsSelected property value is changed.
/// </summary>
public event RoutedEventHandler IsSelectedChanged {
add { AddHandler(IsSelectedChangedEvent, value); }
remove { RemoveHandler(IsSelectedChangedEvent, value); }
}
void RaiseIsSelectionChangedEvent() {
RoutedEventArgs e = new RoutedEventArgs(IsSelectedChangedEvent, this.BatchViewModel);
RaiseEvent(e);
Logger.Debug("IsSelectionChanged: {0}; IsSelected = {1}", this.BatchViewModel.Description, this.IsSelected);
}
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached(
"IsSelected",
typeof(bool),
typeof(BatchView),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(delegate(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
BatchView view = sender as BatchView;
bool isSelected = Convert.ToBoolean(args.NewValue);
if ( view != null ) {
view._border.BorderBrush = isSelected ? Brushes.Magenta : Brushes.Black;
view.IsPrimarySelected = view.IsFocused && isSelected;
}
})));
/// <summary>
/// Get/set whether this batch view is selected
/// </summary>
public bool IsSelected {
get { return (bool)GetValue(IsSelectedProperty); }
set {
if ( IsSelected != value ) {
SetValue(IsSelectedProperty, value);
RaiseIsSelectionChangedEvent();
}
}
}
GanttView (MyContainer):
static GanttView() {
EventManager.RegisterClassHandler(typeof(BatchView), BatchView.IsSelectedChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var batchView = sender as BatchView;
var ganttView = batchView.FindVisualParent<GanttView>();
if ( ganttView != null ) {
ganttView.RaiseBatchViewsSelectionChangedEvent();
}
args.Handled = true;
}));
}
public static readonly RoutedEvent BatchViewsSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"BatchViewsSelectionChanged",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(GanttView)
);
public event RoutedEventHandler BatchViewsSelectionChanged {
add { AddHandler(BatchViewsSelectionChangedEvent, value); }
remove { RemoveHandler(BatchViewsSelectionChangedEvent, value); }
}
void RaiseBatchViewsSelectionChangedEvent() {
RoutedEventArgs e = new RoutedEventArgs(BatchViewsSelectionChangedEvent, this);
RaiseEvent(e);
Logger.Debug("BatchViewsSelectionChanged: {0};", this.SelectedBatchViews.Count());
}
SchedulerView (MyContainerParent):
static SchedulerView() {
EventManager.RegisterClassHandler(typeof(GanttView), GanttView.BatchViewsSelectionChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var schedulerView = ((GanttView)sender).FindVisualParent<SchedulerView>();
if ( schedulerView != null ) {
if ( schedulerView.BatchesSelectionChanged != null ) {
BatchesSelectionChangedEventArgs e = new BatchesSelectionChangedEventArgs();
e.SelectedBatchesCount = schedulerView.GanttView.SelectedBatchViews.Count();
e.TotalBatchesDuration = schedulerView.GanttView.SelectedBatchViews.Sum<BatchView>(bv => bv.BatchViewModel.Model.Duration);
e.TotalBatchesQuantity = schedulerView.GanttView.SelectedBatchViews.Sum<BatchView>(bv => bv.BatchViewModel.Model.Quantity);
schedulerView.BatchesSelectionChanged(schedulerView, e);
}
}
}));
}
If you are concerned about the number of events you have to process. You should re-evaluate your approach. Is there a way to determine when the user has finished selecting the items?
If there is no way of reducing the number of events then you might want to implement a throttle, i.e. you only process an event if no event has been received for a certain amount of time.
You can implement this yourself - e.g. by using a timer - or you can use the reactive extensions' (RX) Throttle function.
Throttle "Ignores the values from an observable sequence which are
followed by another value before due time with the specified source
and dueTime"
You can find the RX at http://msdn.microsoft.com/en-us/data/gg577609.aspx and the documentation for Trottle at http://msdn.microsoft.com/en-us/library/hh229298%28v=vs.103%29. Obviously you can also install RX via NuGet.
I want to share the solution to my problem. Solution is based on recomandation given by my manager and Obalix. So, I will use a timer, to delay a bit the raising of GanttView.BacthViewsSelectionChangedEvent.
static GanttView() {
EventManager.RegisterClassHandler(typeof(BatchView), BatchView.IsSelectedChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var batchView = sender as BatchView;
var ganttView = batchView.FindVisualParent<GanttView>();
if ( ganttView != null && !ganttView._batchViewIsSelectedChangedEventQueued ) {
ganttView._batchViewIsSelectedChangedEventQueued = true;
System.Timers.Timer eventTrigger = new System.Timers.Timer(100) { AutoReset = false };
eventTrigger.Start();
eventTrigger.Elapsed += new System.Timers.ElapsedEventHandler(delegate(object timer, System.Timers.ElapsedEventArgs e) {
ganttView._batchViewIsSelectedChangedEventQueued = false;
ganttView.Dispatcher.Invoke(new Action(delegate() { ganttView.RaiseBatchViewsSelectionChangedEvent(); }), DispatcherPriority.Normal, null);
});
}
args.Handled = true;
}));
}
It is important to use the Dispatcher property for invokation of ganttView.RaiseBatchViewsSelectionChangedEvent(), otherwise you will get an exception (" The calling thread cannot access this object because a different thread owns it."), please refer to this post http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
Dan and Obalix, thank you a lot for your time and considerations!

Frame ContentLoaded event

I'm new at Silverlight.
I've created a sort of master page using a Page with a frame where the content is loaded. As I handle multiple UserControls at the time (only one is shown, but I want to keep the state of the opened before) I'm setting Content property instead of Navigate method. That way I can assign a UserControl (already created, not a new one as it would be using Navigate with the Uri to the UserControl).
Now I want to take a picture as shown here from the frame when its content changes. If I do it immediately when the content set, the UserControl won't be shown in the picture because it takes a few secs. Frames have the event Navigated, but it doesn't fire with property Content (it just fires when the method Navigate is used, as it name says).
How can I know when new Content is loaded?
If it helps I'm using Silverligh 5.
I've a solution but I don't really like it, so I'm still looking for other ways.
public class CustomFrame : Frame
{
private readonly RoutedEventHandler loadedDelegate;
public static readonly DependencyProperty UseContentInsteadNavigationProperty =
DependencyProperty.Register("UseContentInsteadNavigation", typeof (bool), typeof (CustomFrame), new PropertyMetadata(true));
public bool UseContentInsteadNavigation
{
get { return (bool)GetValue(UseContentInsteadNavigationProperty); }
set { SetValue(UseContentInsteadNavigationProperty, value); }
}
public CustomFrame()
{
this.loadedDelegate = this.uc_Loaded;
}
public new object Content
{
get { return base.Content; }
set
{
if (UseContentInsteadNavigation)
{
FrameworkElement fe = (FrameworkElement)value;
fe.Loaded += loadedDelegate;
base.Content = fe;
}
else
{
base.Content = value;
}
}
}
void uc_Loaded(object sender, RoutedEventArgs e)
{
((UserControl)sender).Loaded -= loadedDelegate;
OnContentLoaded();
}
public delegate void ContentLoadedDelegate(Frame sender, EventArgs e);
public event ContentLoadedDelegate ContentLoaded;
private void OnContentLoaded()
{
if (ContentLoaded != null)
ContentLoaded(this, new EventArgs());
}
}

Resources