Performance UserControl / CustomControl - wpf

I already searched a lot of sites on the net, but didn't find any solution. The statement is, that there is no performance difference between a UserControl and a CustomControl.
But I have the following test class X, UserControl, CustomControl and MainWindow:
public class X : INotifyPropertyChanged
{
private string _title;
public string Title
{
get
{
return _title;
}
set
{
if (value == _title)
{
return;
}
_title = value;
OnPropertyChanged("Title");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
UserControl:
<UserControl x:Class="controlperformance.DisplayView"
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">
<Grid Name="root" Background="LightGray">
<TextBlock Text="{Binding Title}" />
</Grid>
</UserControl>
CustomControl:
public class DisplayControl : Control
{
#region Title
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title",
typeof(string),
typeof(DisplayControl),
new PropertyMetadata(default(string)));
#endregion
static DisplayControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DisplayControl), new FrameworkPropertyMetadata(typeof(DisplayControl)));
}
}
Xaml:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DisplayControl}">
<Grid Background="white">
<TextBlock Text="{TemplateBinding Title}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow:
public partial class MainWindow : Window
{
Stopwatch sw = new Stopwatch();
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
sw.Start();
ObservableCollection<X> list = new ObservableCollection<X>();
Random r = new Random();
for (int i = 0; i < 50000; i++)
{
list.Add(new X { Title = r.Next().ToString()});
}
itemscontrol.ItemsSource = list;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
sw.Stop();
MessageBox.Show(sw.Elapsed.ToString());
}
}
MainWindow Content:
<ItemsControl Name="itemscontrol">
<ItemsControl.ItemTemplate>
<!--<DataTemplate DataType="{x:Type Controlperformance:X}">
<Controlperformance:DisplayView DataContext="{Binding}" />
</DataTemplate>-->
<DataTemplate DataType="{x:Type Controlperformance:X}">
<Controlperformance:DisplayControl Title="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When using the CustomControl, the MessageBox shows approx. 20 Seconds on my computer, but when using the UserControl, it takes about a minute! Replacing the Control with its Grid an TextBox, it is even faster than the CustomControl (~16 sec).
Can someone see where the bottleneck is? The problem raises in my real-world application, where the Template/Control would be much more complex.
Thanks a lot,
micro

This is a late answer but the basic difference is that a User Control is almost like a window in that you have the the control itself and then other controls like, buttons, grids, textboxes, etc can be added to it. The basic difference between a window and User Control is that the User Control can and must be displayed within a window.
A Custom Control on the other hand is just a control, it can be used to create a control with a specific functionality for which there are no built in controls or to give an existing control, like buttons, textboxes, etc a specific style to match the theme of your application. You can also add in extra features to existing controls by using a custom control such as adding a label to a text box to show it's purpose.
The difference in loading time is essentially a reflection of the different purposes of User and Custom Controls, with a User Control it load the control and the elements within that control so the loading time may be longer. With a Custom Control only the control itself must load so the so it won't take any longer to load than most built in WPF controls i.e. a button Custom Control shouldn't take longer than a built in button control.

Related

UserControl navigate in owning window wpf

In XAML/WPF I have main window which contains Frame where I intend to put one of the user controls for given view of application.
<Window x:Class="MyApp.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:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot">
<Frame Source="Main/MainUserControl.xaml" Name="Main" />
</Grid>
</Window>
Now I want to navigate this Frame to other source inside MainUserControl:
<UserControl x:Class="MyApp.View.MainMenu.MainMenuUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
xmlns:command="clr-namespace:MyApp.Command"
mc:Ignorable="d"
Style="{StaticResource Localizable}"
DataContext="{Binding MainMenu, Source={StaticResource Locator}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Content="{lex:Loc About}" FontSize="28" Grid.Row="1" Command="NavigationCommands.GoToPage" CommandParameter="/Menu/AboutUserControl.xaml" />
</Grid>
</UserControl>
But the navigation button About remains inactive during execution. I verified correctly that /Menu/AboutUserControl.xaml exists.
I'm obviously doing something wrong. How can I navigate owning window's frame from within user control? Preferably via XAML?
I assume you are using an MVVM framework. (I have added the critical elements here in case you aren't).
Your MainWindow.xaml should use an "ItemsControl" instead of a "Frame". A frame can work, but a better way is to use the ItemsControl like so:
<!-- Main Frame -->
<Grid Grid.Column="1" Margin="10" Name="MainWindowFrameContent">
<ItemsControl ItemsSource="{Binding Path=MainWindowFrameContent}" >
<!-- This controls the height automatically of the user control -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
In the constructor of my MainWindow.cs, I set the DataContext of the window to the MainViewModel:
using myProject.ViewModel;
public partial class MainWindow : Window
{
MainViewModel mMainViewModel;
public MainWindow()
{
InitializeComponent();
// Initialize MainViewModel and set data context to the VM
mMainViewModel = new MainViewModel();
DataContext = mMainViewModel;
}
}
(I'm not sure if this next part HAS TO be an observable collection, but I have implemented it as such and it seems to work well. The only downside is that I need to manually clear the ItemsControl before adding a new UserControl)
My MainViewModel implements the binding called "MainWindowFrameContent". All of my user controls are initialized within the MainViewModel.cs code. I have an additional ViewModel for each UserControl and assign the DataContext of the UserControl to the individual ViewModel before displaying the UserControl to the main window.
My MainViewModel.cs:
public class MainViewModel : ObservableObject
{
public MainViewModel()
{
}
// This handles adding framework (UI) elements to the main window frame
ObservableCollection<FrameworkElement> _MainWindowFrameContent = new ObservableCollection<FrameworkElement>();
public ObservableCollection<FrameworkElement> MainWindowFrameContent
{
get
{
return _MainWindowFrameContent;
}
set
{
_MainWindowFrameContent = value;
RaisePropertyChangedEvent("MainWindowFrameContent");
}
}
// This handles opening a generic user control on the main window
// The ICommand implementation allows me to bind the command of a button on the main window to displaying a specific page
public ICommand MainWindowDataEntryView
{
get
{
return new DelegateCommand(_MainWindowDataEntryView);
}
}
void _MainWindowDataEntryView(object obj)
{
DataEntryVM wDataEntryVM = new DataEntryVM();
DataEntryView wDataEntryView = new DataEntryView();
wDataEntryView.DataContext = wDataEntryVM;
MainWindowFrameContent.Clear();
MainWindowFrameContent.Add(wDataEntryView);
}
}
Then you need to make sure you have an ObservableObject.cs as part of your project:
using System.ComponentModel;
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And you need a DelegateCommand.cs class as part of your project:
using System;
using System.Windows.Input;
public class DelegateCommand : ICommand
{
private readonly Action<object> _action;
public DelegateCommand(Action<object> action)
{
_action = action;
}
public void Execute(object parameter)
{
_action(parameter);
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged { add { } remove { } }
#pragma warning restore 67
}
So, it's a bit of a lengthy explanation, but once you have the previous items set up, you can add a bunch of buttons to your MainWindow.xaml, bind each button to a command that adds a new UserControl to your ItemsControl. When your UserControl displays, you can add controls as you would like and use them.
I hope this helps!

How do I Bind WPF Commands between a UserControl and a parent Window

I'l start by letting a picture do some talking.
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
Many thanks for any insights,
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
First, our Window
<Window x:Class="UCsAndICommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:UCsAndICommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<t:ViewModel />
</Window.DataContext>
<t:ItemsEditor Items="{Binding Items}"
AddItem="{Binding AddItem}"
RemoveItem="{Binding RemoveItem}" />
</Window>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
Next, the UserControl
<UserControl x:Class="UCsAndICommands.ItemsEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.
Here's the definitions of these properties on the UC
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
public class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public ICommand RemoveItem { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
AddItem = new DelegatedCommand<object>(
o => true, o => Items.Add(new Item()));
RemoveItem = new DelegatedCommand<Item>(
i => true, i => Items.Remove(i));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
Refer the below code.
UserControl.XAML
<Grid>
<ListBox ItemsSource="{Binding Things}" x:Name="lst">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ThingName}" Margin="3"/>
<Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Window.Xaml
<Window x:Class="MultiBind_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBind_Learning"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
<local:UserControl2/>
</StackPanel>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
class ThingViewModel
{
private ObservableCollection<Thing> things = new ObservableCollection<Thing>();
public ObservableCollection<Thing> Things
{
get { return things; }
set { things = value; }
}
public ICommand AddCommnd { get; set; }
public ICommand RemoveCommand { get; set; }
public ThingViewModel()
{
for (int i = 0; i < 10; i++)
{
things.Add(new Thing() { ThingName="Thing" +i});
}
AddCommnd = new BaseCommand(Add);
RemoveCommand = new BaseCommand(Remove);
}
void Add(object obj)
{
things.Add(new Thing() {ThingName="Added New" });
}
void Remove(object obj)
{
things.Remove((Thing)obj);
}
}
Thing.cs
class Thing :INotifyPropertyChanged
{
private string thingName;
public string ThingName
{
get { return thingName; }
set { thingName = value; OnPropertyChanged("ThingName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.

Extremely simple silverlight binding not working

This should be an extremely simple solution, but searching through the internet there seems to be multiple different ways to do binding and NONE seem to actually work.
I've created a simple application with a button, textbox and listbox. The user adds text to the textbox, clicks Add and I want the text to appear in the list box. Note that the Add button will create a Person with the firstname the text in the textbox and the last name "Jones". This is just to figure out how to get binding to actually work. I have the ObservableCollection but can't seem to even figure out how to put in the resource to the object within the class itself. Is this even possible? do I have to create a separate class to have a binding?
Here is the complete XMAL
<UserControl x:Class="simpleBinding.MainPage"
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:z="clr-namespace:simpleBinding"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Canvas x:Name="LayoutRoot" Background="White">
<Button Name="_b" Content="Add" Height="23" HorizontalAlignment="Left" VerticalAlignment="Top" Width="58" Canvas.Left="90" Canvas.Top="5" Click="OnAdd" />
<TextBox Name="_tb" Canvas.Left="12" Canvas.Top="4" Height="24" Width="72"></TextBox>
<ListBox Name="_list" Canvas.Left="18" Canvas.Top="41" Height="98" Width="190" />
</Canvas>
and here is the complete Code behind
namespace simpleBinding
{
public partial class MainPage : UserControl
{
public ObservableCollection<Person> PersonList = new ObservableCollection<Person> ();
public MainPage()
{
InitializeComponent();
}
private void OnAdd(object sender, RoutedEventArgs e)
{
PersonList.Add(new Person(_tb.Text, "Jones"));
}
}
public class Person
{
public string FirstName {private set; get;}
public string LastName {private set; get; }
public Person(string fName, string lName)
{
FirstName = fName;
LastName = lName;
}
}
}
thanks for any help,
chris
To illustrate Ravuthasamy's & aqwert's comments. You have to set a DataContext first. You can set this in DataContext or read how MVVM work (It's a good Silvelight binding pattern) :
c#
public MainPage()
{
InitializeComponent();
DataContext = this;
}
After you can bind the class properties to elements :
Xaml
<ListBox
ItemsSource="{Binding PersonList}"
Canvas.Left="18"
Canvas.Top="41"
Height="98"
Width="190" />
Following the timeline you can see that this has taken me a week to finally get to a solution. I post it here now in hopes that someone else won't waste this much time. There seems to be a lot of posts about how to deal with this issue and the examples are limited. They either show only C# or Xaml. Then CollectionChanged and PropertyChanged aren't dealt with in a single example.
This is a simple example, that implements both collection changed and property changed. As well as binding in Xaml
Here is the Xaml.
<UserControl x:Class="simpleBinding.MainPage"
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:src="clr-namespace:simpleBinding"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Canvas x:Name="LayoutRoot" Background="White" DataContext="{Binding}">
<Canvas.Resources>
<src:PersonList x:Key="myDataSource"></src:PersonList>
</Canvas.Resources>
<Button Name="_b" Content="Add" Height="23" HorizontalAlignment="Left" VerticalAlignment="Top" Width="58" Canvas.Left="90" Canvas.Top="5" Click="OnAdd" />
<Button Canvas.Left="150" Canvas.Top="5" Content="Edit" Height="23" Name="button1" Width="58" Click="OnEdit" />
<TextBox Name="_tb" Canvas.Left="12" Canvas.Top="4" Height="24" Width="72"></TextBox>
<ListBox Name="_list" Canvas.Left="18" Canvas.Top="41" Height="98" Width="190" ItemsSource="{Binding Source={StaticResource myDataSource}}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}" Margin="0,0,2,0" />
<TextBlock Text="{Binding Path=LastName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Canvas>
Add a xmlns that will reference your code behind. In this case my namespace is xmlns:src then you can use VS intellisense to go to the correct class.
Add a resource to the layoutRoot item. In my case I'm using a canvas, but it could be Grid or Stackpanel etc.
With the resource declared, you can now set the ItemSource binding in the ListBox.
I've chosen to use a template to display the data which I think is really cool (best part of Xaml!) In this case there are two textBlocks but if my underlying data source had an image, I could have used this was well to graphically display the data. The binding for each textbox can be set because the exposed properties of the object are declared in the C# code. Which will be discussed next
C# Code behind
namespace simpleBinding
{
public partial class MainPage : UserControl
{
public PersonList m_pList = new PersonList();
public MainPage()
{
InitializeComponent();
_list.ItemsSource = m_pList;
m_pList.Add(new Person("John", "Doe"));
}
private void OnAdd(object sender, RoutedEventArgs e)
{
m_pList.Add(new Person("Jones", _tb.Text));
}
private void OnEdit(object sender, RoutedEventArgs e)
{
m_pList[1].FirstName = _tb.Text;
}
}
public class PersonList : ObservableCollection<Person> , INotifyPropertyChanged
{
public PersonList() : base() // need to call base on intialization otherwise the binded resource is not updated.
{
Add(new Person("Willa", "Cather"));
Add(new Person("Isak", "Dinesen"));
Add(new Person("Victor", "Hugo"));
Add(new Person("Jules", "Verne"));
}
}
public class Person : INotifyPropertyChanged
{
private string _fName;
private string _lName;
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName
{
set
{
_fName = value;
NotifyPropertyChanged("FirstName");
}
get
{
return _fName;
}
}
public string LastName
{
set
{
_lName = value;
NotifyPropertyChanged("LastName");
}
get
{
return _lName;
}
}
public Person(string fName, string lName) : base()
{
FirstName = fName;
LastName = lName;
}
public override string ToString()
{
return String.Format("{0} {1}", FirstName, LastName);
}
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I've chosen to use the ObservableCollection because it implements INotifyCollectionChanged. The public variable is exposed which allows you to bind to the resource declared in the Xaml. (Better code, make the var private and have a property that exposes the variable through a get!)
The ListBox _List needs to have its ItemsSource property set in Code Behind!!! without this whenever you change the list (add, delete etc) the UI is not updated. AND in fact you do not need the binding in the ListBox at all because we set the source in Code behind it is nice however in that in the designer with this bound control you can see that the binding is working because there are four names added when instantiating the PersonList.
The ObservableCollection needs to have the INotifyCollectionChanged added. Without this, when a property is changed the UI is NOT changed.
The properties that are to be exposed to the UI need to be implement in the object that is contained within the ObservableCollection (in my case the class Person exposed both FirstName and LastName) and then these properties can be bound in the Xaml (see the textBlocks's)
INotifyPropertyChanged requires that you implement a PropertyChanged event i.e. public event PropertyChangedEventHandler PropertyChanged;
To actually fire that event the "Person" object needs to implement code to do that, which in my case is the NotifyPropertyChanged Method. Each time a property is set, I call this method, which in turn looks to see is the PropertyChanged event is not null, and if not, then it raises that event.
Here is the key to property changes, without adding the , INotifyPropertyChanged to the Observable collection PropertyChanged is null.
Hope this helps someone

Keeping a WPF UI responsive while showing a slow loading UserControl

We have a WPF application written using the MVVM pattern. Within the application is a TabControl with different UserControls within each tab. Under certain conditions one of the UserControls on a tab can take a significant portion of time to load when switching to the containing tab.
This is NOT because of any performance bottlenecks in the ViewModel. But instead, is due to significant amount of time that the usercontrol takes to bind to the ViewModel, and to create the various UI elements contained within it and initialize them.
When the user clicks on the tab for this usercontrol, the UI becomes completely unresponsive until the control has completed loading. If fact you don't even see the "active tab" switch until everything is loaded.
What strategies could I use to display a "spinner" with some sort of "please wait, loading..." message while waiting for the UI elements to complete loading?
UPDATE with sample code:
The below demonstrates the type of problem I am trying to get around. When you click on the "slow tab". The UI becomes unresponsive until all the items in the slow tab have rendered.
In the below, TestVM is the viewmodel for the slow tab. It has a large collection of children objects. Each created with it's own data template.
How could I display a "loading" message while the slow tab finishes loading?
public class MainVM
{
private TestVM _testVM = new TestVM();
public TestVM TestVM
{
get { return _testVM; }
}
}
/// <summary>
/// TestVM is the ViewModel for the 'slow tab'. It contains a large collection of children objects that each will use a datatemplate to render.
/// </summary>
public class TestVM
{
private IEnumerable<ChildBase> _children;
public TestVM()
{
List<ChildBase> list = new List<ChildBase>();
for (int i = 0; i < 100; i++)
{
if (i % 3 == 0)
{
list.Add(new Child1());
}
else if (i % 3 == 1)
{
list.Add(new Child2());
}
else
{
list.Add(new Child3());
}
}
_children = list;
}
public IEnumerable<ChildBase> Children
{
get { return _children; }
}
}
/// <summary>
/// Just a base class for a randomly positioned VM
/// </summary>
public abstract class ChildBase
{
private static Random _rand = new Random(1);
private int _top = _rand.Next(800);
private int _left = _rand.Next(800);
public int Top { get { return _top; } }
public int Left { get { return _left; } }
}
public class Child1 : ChildBase { }
public class Child2 : ChildBase { }
public class Child3 : ChildBase { }
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!-- Template for the slow loading tab -->
<DataTemplate DataType="{x:Type local:TestVM}">
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="Canvas.Top" Value="{Binding Top}"></Setter>
<Setter Property="Canvas.Left" Value="{Binding Left}"></Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
<!-- examples of different child templates contained in the slow rendering tab -->
<DataTemplate DataType="{x:Type local:Child1}">
<DataGrid></DataGrid><!--simply an example of a complex control-->
</DataTemplate>
<DataTemplate DataType="{x:Type local:Child2}">
<RichTextBox Width="30" Height="30">
<!--simply an example of a complex control-->
</RichTextBox>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Child3}">
<Calendar Height="10" Width="15"></Calendar>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl>
<TabItem Header="Fast Loading tab">
<TextBlock Text="Not Much Here"></TextBlock>
</TabItem>
<TabItem Header="Slow Tab">
<ContentControl Content="{Binding TestVM}"></ContentControl>
</TabItem>
</TabControl>
</Grid>
</Window>
What do u need is here
http://msdn.microsoft.com/en-us/library/ms741870.aspx
public partial class Window1 : Window
{
// Delegates to be used in placking jobs onto the Dispatcher.
private delegate void NoArgDelegate();
private delegate void OneArgDelegate(String arg);
// Storyboards for the animations.
private Storyboard showClockFaceStoryboard;
private Storyboard hideClockFaceStoryboard;
private Storyboard showWeatherImageStoryboard;
private Storyboard hideWeatherImageStoryboard;
public Window1(): base()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Load the storyboard resources.
showClockFaceStoryboard =
(Storyboard)this.Resources["ShowClockFaceStoryboard"];
hideClockFaceStoryboard =
(Storyboard)this.Resources["HideClockFaceStoryboard"];
showWeatherImageStoryboard =
(Storyboard)this.Resources["ShowWeatherImageStoryboard"];
hideWeatherImageStoryboard =
(Storyboard)this.Resources["HideWeatherImageStoryboard"];
}
private void ForecastButtonHandler(object sender, RoutedEventArgs e)
{
// Change the status image and start the rotation animation.
fetchButton.IsEnabled = false;
fetchButton.Content = "Contacting Server";
weatherText.Text = "";
hideWeatherImageStoryboard.Begin(this);
// Start fetching the weather forecast asynchronously.
NoArgDelegate fetcher = new NoArgDelegate(
this.FetchWeatherFromServer);
fetcher.BeginInvoke(null, null);
}
private void FetchWeatherFromServer()
{
// Simulate the delay from network access.
Thread.Sleep(4000);
// Tried and true method for weather forecasting - random numbers.
Random rand = new Random();
String weather;
if (rand.Next(2) == 0)
{
weather = "rainy";
}
else
{
weather = "sunny";
}
// Schedule the update function in the UI thread.
tomorrowsWeather.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new OneArgDelegate(UpdateUserInterface),
weather);
}
private void UpdateUserInterface(String weather)
{
//Set the weather image
if (weather == "sunny")
{
weatherIndicatorImage.Source = (ImageSource)this.Resources[
"SunnyImageSource"];
}
else if (weather == "rainy")
{
weatherIndicatorImage.Source = (ImageSource)this.Resources[
"RainingImageSource"];
}
//Stop clock animation
showClockFaceStoryboard.Stop(this);
hideClockFaceStoryboard.Begin(this);
//Update UI text
fetchButton.IsEnabled = true;
fetchButton.Content = "Fetch Forecast";
weatherText.Text = weather;
}
private void HideClockFaceStoryboard_Completed(object sender,
EventArgs args)
{
showWeatherImageStoryboard.Begin(this);
}
private void HideWeatherImageStoryboard_Completed(object sender,
EventArgs args)
{
showClockFaceStoryboard.Begin(this, true);
}
}
P.S. Perhaps it is useful as well http://tech.pro/tutorial/662/csharp-tutorial-anonymous-delegates-and-scoping and Make dispatcher example to work
Make your control lazy-load its contents.
To do that expose an ObservableCollection property in your TestVM class and attach event handlers to CollectionChanged (possible PropertyChanged too) to add actual UI elements.
In Window1 prepare the data to load in TestVM on a separate thread (are you doing any web queries?), pass the data to TestVM on the UI thread.
If TestVM child controls themselves load slowly you can split that drive that process from a separate thread too but that's (way) more difficult to pull of, so hopefully it's the data loading that's the slow part
Causes could be Slow Code in a Binding Converter, Coerce Value Callback, Properties could all make a binding appear slow. For Example, Consider An Image whose source binds to a URL. This could load slow due to network lags.
Also avoid switching to dispatcher context - unless really needed. For example to launch threads, Waiting for WaitHandles, even large/slow synchronous I/O operations etc etc
Sten Petrov's suggestion to lazy load (UI and Data Virtualization) is vital too.

Binding to custom dependency property - again

The task: implement the simplest Dependency Property ever, which can be used in xaml like that:
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
I think that this answer is quite close. For better readability i copy all my code here (mostly from that answer above).
<UserControl x:Class="Test.UserControls.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<!-- Text is being bound to outward representative property;
Note the DataContext of the UserControl -->
<TextBox Text="{Binding MyTextProperty}"/>
</Grid>
</UserControl>
and
public partial class MyUserControl1 : UserControl
{
// The dependency property which will be accessible on the UserControl
public static readonly DependencyProperty MyTextPropertyProperty =
DependencyProperty.Register("MyTextProperty", typeof(string), typeof(MyUserControl1), new UIPropertyMetadata(String.Empty));
public string MyTextProperty
{
get { return (string)GetValue(MyTextPropertyProperty); }
set { SetValue(MyTextPropertyProperty, value); }
}
public MyUserControl1()
{
InitializeComponent();
}
}
And this is my MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:uc="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<uc:MyUserControl1 MyTextProperty="my text goes here"/>
<Button Click="ButtonBase_OnClick" Content="click"/>
</StackPanel>
</Window>
So far, everything works. However, i find this quite not usefull. What i'd need is
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
and being able to change this by setting a DataContext (as you usually do in MVVM)
So i replace the line as above and add my code behind as follows:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
Text = "Initial Text";
DataContext = this;
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
if (value != _Text)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Text = "clicked";
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Neither the "initial Text" nor the "clicked" is displayed... ever. So my question is how to implement a dept. property correctly to be used with
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
The Text property is located on the DataContext of the MainWindow not of the UserControl.
So change this line <uc:MyUserControl1 MyTextProperty="{Binding Text}"/> into this:
<uc:MyUserControl1 MyTextProperty="{Binding Text, ElementName=MyMainWindow}"/>
Which will tell the Binding that you're talking about the Text element located in you MainWindow. Of course, since in this example I used ElementName, you're going to want to name your window MyMainWindow...
So add this to your MainWindow:
<Window Name="MyMainWindow" ..... />
If you rather not name your window, you can use the RelativeSource FindAncestor binding like this:
<wpfApplication6:MyUserControl1 MyTextProperty="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"/>
In both ways, you are asking to find the property named 'Text' in the DataContext of the window.

Resources