Multithreading freezes UI - wpf

I have a mutlithreaded WPF app. But after some background threads are launched, the UI is freezing (the display and the UI inputs).
Here is a simplified (but complete) example :
<Window x:Class="WPF_Multithreading.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Path=Scores, UpdateSourceTrigger=PropertyChanged}"/>
<ListBox Grid.Column="1" ItemsSource="{Binding Path=FrameRates, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Row="1"
Content="New Thread" Click="Button_NewThread_Click" />
<Button Grid.Row="1" Grid.Column="1"
Content="UI test" Click="Button_UItest_Click"/>
</Grid>
</Window>
code-behind :
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Threading;
namespace WPF_Multithreading
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private List<Thread> threads;
ObservableCollection<int> _scores;
public ObservableCollection<int> Scores
{
get { return _scores; }
set { _scores = value; RaisePropertyChanged("Scores"); }
}
ObservableCollection<int> _framerates;
public ObservableCollection<int> FrameRates
{
get { return _framerates; }
set { _framerates = value; RaisePropertyChanged("FrameRates"); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Scores = new ObservableCollection<int>();
FrameRates = new ObservableCollection<int>();
threads = new List<Thread>();
}
~MainWindow()
{
foreach (Thread t in threads) t.Abort();
}
private void Button_NewThread_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(backgroundRun);
Scores.Add(0);
FrameRates.Add(0);
t.Start(this);
threads.Add(t);
}
private void backgroundRun(object o)
{
MainWindow _this = (MainWindow)o;
int index = _this.Scores.Count()-1;
Random r = new Random();
DateTime prevDT = DateTime.Now;
int nbiterations = 0;
while(true)
{
// simulate computation
nbiterations = 0;
while((DateTime.Now-prevDT).Milliseconds < 50)
{
nbiterations++;
}
// Update UI
Dispatcher.Invoke(() =>
{
_this.Scores[index] = nbiterations;// r.Next();
_this.FrameRates[index] = (DateTime.Now - prevDT).Milliseconds;
});
prevDT = DateTime.Now;
}
}
private void Button_UItest_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("UI input ok");
}
}
}
In this example I have to launch a lot of threads to make UI freezing but in my more complex app (displaying 2D graphics), only 2 or 3 threads would make it happen (with freezing for more than 10 seconds sometimes).
My question is then : is there a way to ensure the Dispatcher to have enough resource to unfreeze UI (especially inputs) ?

Related

Caliburn Micro MVVM : How to subscribe and unsubscribe event in ViewModel?

I created a WPF sample (using caliburn micro with MVVM pattern, no code-behind) with a view model and their related views:
ShellView.xaml and ShellViewModel.cs
The ShellView contains:
A ComobBox, which contains a list of string, if this combox selection is changed, it will raise comboBox1_SelectionChanged() in ShellViewModel.
A Button, if click this button, it will raise Button1_Click() to delete the first item of list in ShellViewModel.
My questions:
If I want to click the button without trigger comboBox1_SelectionChanged in view model, how to do that?
If it implemented in code-behind, I can do like this:
public void Button1_Click(object sender, EventArgs e)
{
comboBox1.SelectionChanged -= comboBox1_SelectionChanged;
MyCollection.RemoveAt(0);
comboBox1.SelectionChanged += comboBox1_SelectionChanged;
}
I have no idea how to achieve this in view model. The following is the code:
ShellView.xaml
<UserControl x:Class="WpfApp.Views.ShellView"
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:local="clr-namespace:WpfApp.Views"
xmlns:cal="http://caliburnmicro.com"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=" auto"/>
<RowDefinition Height=" auto"/>
</Grid.RowDefinitions>
<ComboBox Name="comboBox1" Grid.Row="0" ItemsSource="{Binding MyCollection}" SelectedValue="{Binding SelectMyListValue}"
cal:Message.Attach="[Event SelectionChanged]=[Action comboBox1_SelectionChanged($source,$eventArgs)]" />
<Button Name="Button1" Grid.Row="1" Content="Delete"
cal:Message.Attach="[Event Click]=[Action Button1_Click($source,$eventArgs)]" />
</Grid>
</UserControl>
ShellViewModel.cs
using Caliburn.Micro;
using System;
using System.Windows.Controls;
namespace WpfApp.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.OneActive
{
private BindableCollection<string> _myCollection = new BindableCollection<string>() { "item1", "item2"};
public BindableCollection<string> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
NotifyOfPropertyChange(() => MyCollection);
}
}
private string _selectMyListValue = "item1";
public string SelectMyListValue
{
get => _selectMyListValue;
set
{
_selectMyListValue = value;
NotifyOfPropertyChange(nameof(SelectMyListValue));
}
}
public void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Do something...
}
public void Button1_Click(object sender, EventArgs e)
{
MyCollection.RemoveAt(0);
}
}
}
Thank you in advance.
Your requirement can't be fully met, as when you remove the selected item from the collection a change of SelectedValue (to null) is inevitable.
Furthermore: You don't need to bind to the SelectionChanged event. You already have a binding to SelectedValue, so the setter of the bound property is called when the selection changes. This doesn't happen, when you remove a value from the collection that is not currently selected.
I would also recommend not to subscribe to the Clicked event of the button, but to bind an ICommand (added to your viewmodel) to the Command property of the button. An easy to use implementation would be the RelayCommand from the Windows Community Toolkit. You can read about it here: https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/relaycommand. It also isn't difficult to implemnt a version on your own, if you don't want to use the whole toolkit.
Code sample:
public class RelayCommand : ICommand
{
private readonly Action<object?> execute;
private readonly Func<object?, bool> canExecute;
public RelayCommand(
Action<object?> execute,
Func<object?, bool>? canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute ?? (_ => true);
}
public bool CanExecute(object? parameter) => this.canExecute(parameter);
public void Execute(object? parameter)
{
this.execute(parameter);
}
public event EventHandler? CanExecuteChanged;
}
// on your viewmodel add...
public ICommand RemoveFirstItemCommand { get; set; }
private void RemoveFirstItem(object? param)
{
if (this.Items.Count > 0)
{
this.Items.RemoveAt(0);
}
}
// ...and in the constructor init the command
this.RemoveFirstItemCommand = new RelayCommand(this.RemoveFirstItem);
I got a solution which achieved the goal, but I'm not sure if it's the right way.
There is a "Microsoft.Xaml.Behaviors" which provided "Interaction.Triggers" that contains "ComparisonCondition". I can use it to bind a value to determine the EventCommand is raised or not.
I updated the code as following:
ShellViewModel.cs
using Caliburn.Micro;
using System;
using System.Windows.Controls;
using WpfApp.Commands;
namespace WpfApp.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.OneActive
{
private bool _IsEnableSelectionChangedCommand = true;
public bool IsEnableSelectionChangedCommand
{
get => _IsEnableSelectionChangedCommand;
set
{
_IsEnableSelectionChangedCommand = value;
NotifyOfPropertyChange(() => IsEnableSelectionChangedCommand);
}
}
private BindableCollection<string> _myCollection = new BindableCollection<string>() { "item1", "item2"};
public BindableCollection<string> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
NotifyOfPropertyChange(() => MyCollection);
}
}
private string _selectMyListValue = "item1";
public DelegateCommand<object> DoSelectionChangedCommand { get; }
public ShellViewModel()
{
DoSelectionChangedCommand = new DelegateCommand<object>(comboBox1_SelectionChanged, CanExecute);
}
private bool CanExecute(object param)
{
return true;
}
private void comboBox1_SelectionChanged(object param)
{
SelectionChangedEventArgs e = param as SelectionChangedEventArgs;
ComboBox item = e.Source as ComboBox;
// Do something...
}
public string SelectMyListValue
{
get => _selectMyListValue;
set
{
_selectMyListValue = value;
NotifyOfPropertyChange(nameof(SelectMyListValue));
}
}
public void Button1_Click(object sender, EventArgs e)
{
IsEnableSelectionChangedCommand = false;
MyCollection.RemoveAt(0);
IsEnableSelectionChangedCommand = true;
}
}
}
ShellView.xaml
<UserControl x:Class="WpfApp.Views.ShellView"
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:cal="http://caliburnmicro.com"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:cmd="clr-namespace:WpfApp.Commands"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=" auto"/>
<RowDefinition Height=" auto"/>
</Grid.RowDefinitions>
<ComboBox Name="comboBox1" Grid.Row="0" ItemsSource="{Binding MyCollection}" SelectedValue="{Binding SelectMyListValue}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventCommand Command="{Binding DoSelectionChangedCommand}" />
<i:Interaction.Behaviors>
<i:ConditionBehavior>
<i:ConditionalExpression>
<i:ComparisonCondition LeftOperand= "{Binding IsEnableSelectionChangedCommand}" Operator="Equal" RightOperand="True"/>
</i:ConditionalExpression>
</i:ConditionBehavior>
</i:Interaction.Behaviors>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Button Name="Button1" Grid.Row="1" Content="Delete"
cal:Message.Attach="[Event Click]=[Action Button1_Click($source,$eventArgs)]" />
</Grid>
</UserControl>

Unable to see the Date time text on the WPF application screen

I am trying to write a simple MVVM application. The expectation of this sample is to show up the time on the screen.
The xaml designer showed the time once in the designer
but when I run the app, I dont see the time on the screen at all.
Could you please help me find what the issue is?
I am attaching the code here.
MainWindow.xaml
<Window x:Class="WPFDigitalClock.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:WPFDigitalClock"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ClockVM/>
</Window.DataContext>
<Grid>
<StackPanel>
<TextBlock Foreground="White" Background="Black" FontSize="30" TextAlignment="Center" Text="{Binding Path=DisplayTime}"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WPFDigitalClock
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
ClockVM.cs
using System;
using System.ComponentModel;
using System.Windows.Threading;
namespace WPFDigitalClock
{
class ClockVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ClockModel clockModel;
DispatcherTimer Timer = new DispatcherTimer();
public string DisplayTime { get; set; }
public ClockVM()
{
clockModel = new ClockModel();
DisplayTime = "";
Timer.Interval = new TimeSpan(0, 0, 1);
Timer.Tick += new EventHandler(Timer_Click);
Timer.Start();
}
private void Timer_Click(object sender, EventArgs e)
{
DisplayTime = clockModel.Hour + ":" + clockModel.Minute + ":" + clockModel.Second;
if (PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs("DisplayTime"));
}
}
}
}
ClockModel.cs
using System;
namespace WPFDigitalClock
{
class ClockModel
{
public string Hour
{
get { return DateTime.Now.Hour.ToString(); }
}
public string Minute
{
get { return DateTime.Now.Minute.ToString(); }
}
public string Second
{
get { return DateTime.Now.Second.ToString(); }
}
}
}
Problem is with how you have Raised the change of property DisplayTime. use belowDispatcherTimer.Tick handler as:
private void Timer_Click(object sender, EventArgs e)
{
DisplayTime = clockModel.Hour + ":" + clockModel.Minute + ":" + clockModel.Second;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DisplayTime"));
}
}
You have passed the sender object as argument in PropertyChanged method. It expect owner class of Property
DisplayTime in sender argument.
Cannot reproduce your error. I've just added a counter and it updates perfectly.
int counter = 0;
private void Timer_Tick(object sender, EventArgs e)
{
DisplayTime = clockModel.Hour + ":" + clockModel.Minute + ":" +
clockModel.Second + " Counter:" + counter++.ToString();
OnPropertyChanged("DisplayTime");
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
My xaml is:
<Window x:Class="DataGridSelectedItemsWpfApplication.MainWindow"
<!--The code omitted for the brevity-->
xmlns:vm="clr-namespace:DataGridSelectedItemsWpfApplication.ViewModel"
Title="MainWindow" WindowStartupLocation="CenterScreen" Height="350" Width="525">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding DisplayTime}"/>
</Grid>
</Window>
The model is the same like your model.
This works like this:
Update:
As #KyloRen says correctly about your mistake. Just change from
PropertyChanged(SENDER, new PropertyChangedEventArgs(propertyName)); TO
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));. It means that you raise an event from ClockVM.

Why is my busy page not working?

I'm trying to develop a busy Window as said here and here. I want a grid to be visible whenver I need it to be visible, for example while I'm doing a long task.
I did this til now:
The XAML:
<Window x:Class="LoadingWindow2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loadingWindow2="clr-namespace:LoadingWindow2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<loadingWindow2:BoolToVisibilityConverter x:Key="boolConverter"/>
</Window.Resources>
<Grid>
<Label HorizontalAlignment="Right">
<Hyperlink Command="{Binding Path=DoSomething}">Do Something</Hyperlink>
</Label>
<Border BorderBrush="Black" BorderThickness="1" Background="#80000000" Visibility="{Binding IsBusy, Converter={StaticResource boolConverter}}" Grid.RowSpan="3">
<Grid>
<TextBlock Margin="0" TextWrapping="Wrap" Text="Please Wait..." HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" FontWeight="Bold" Foreground="#7EFFFFFF"/>
</Grid>
</Border>
</Grid>
My .cs:
The BusyViewModel.cs:
public class BusyViewModel : INotifyPropertyChanged
{
private ICommand doSomethingCommand;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsBusy { get; set; }
public ICommand DoSomething
{
get { return doSomethingCommand ?? (doSomethingCommand = new DelegateCommand(LongRunningTask)); }
}
private void LongRunningTask()
{
var task = new Task(ComputeResults);
task.Start();
}
private void ComputeResults()
{
this.IsBusy = true;
Thread.Sleep(5000);
this.IsBusy = false;
}
}
The DelegateCommand.cs:
public class DelegateCommand : ICommand
{
private readonly Action executeMethod;
private readonly Func<bool> canExecuteMethod;
public DelegateCommand(Action executeMethod)
: this(executeMethod, () => true)
{
}
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
{
if (executeMethod == null)
throw new ArgumentNullException("executeMethod");
if (canExecuteMethod == null)
throw new ArgumentNullException("canExecuteMethod");
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object stupid)
{
return CanExecute();
}
public bool CanExecute()
{
return canExecuteMethod();
}
public void Execute(object parameter)
{
Execute();
}
public void Execute()
{
executeMethod();
}
public void OnCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
this.DataContext = new BusyViewModel();
}
I downloaded the source code from the first link I copied, and the busy Grid is showing. But in my case... is NOT!!! What I'm doing wrong here?
EDIT: I deleted the Converter as suggested. But it's not working yet... I add my `MainWindow.xaml.cs"
The whole source: here
There is a converter available from WPF already, the "booleanToVisibilityConverter" that does the job.
See http://msdn.microsoft.com/de-de/library/system.windows.controls.booleantovisibilityconverter%28v=vs.110%29.aspx
Edit your xaml like this:
<Window x:Class="LoadingWindow2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loadingWindow2="clr-namespace:LoadingWindow2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="boolConverter"/>
</Window.Resources>
<Grid x:Name="ROOT">
<Label HorizontalAlignment="Right">
<Hyperlink Command="{Binding Path=DoSomething}">Do Something</Hyperlink>
</Label>
<Border BorderBrush="Black" BorderThickness="1" Background="#80000000" Visibility="{Binding Path=IsBusy, Converter={StaticResource boolConverter}}" Grid.RowSpan="3">
<Grid>
<TextBlock Margin="0" TextWrapping="Wrap" Text="Please Wait..."
horizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" FontWeight="Bold" Foreground="#7EFFFFFF"/>
</Border>
</Grid>
EDIT: Implementation of BusyViewMOdel
public class BusyViewModel : INotifyPropertyChanged
{
private ICommand doSomethingCommand;
public event PropertyChangedEventHandler PropertyChanged;
private bool _isBusy = false;
public bool IsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
OnPropertyChanged("IsBusy");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ICommand DoSomething
{
get { return doSomethingCommand ?? (doSomethingCommand = new DelegateCommand(LongRunningTask)); }
}
private void LongRunningTask()
{
var task = new Task(ComputeResults);
task.Start();
}
private void ComputeResults()
{
this.IsBusy = true;
Thread.Sleep(5000);
this.IsBusy = false;
}
}
See http://msdn.microsoft.com/de-de/library/ms743695%28v=vs.110%29.aspx
EDIT: Try to set the data context on the root grid by giving the grid a name and instead of this.DataContext = ... in public MainWindow() ...do ROOT.DataContext = .... See updated xaml!
EDIT: Got it working. See this code of class BusyViewModel.
private void LongRunningTask()
{
var task = new Task(ComputeResults);
task.Start();
}
private void ComputeResults()
{
this.IsBusy = true; // you did _isBusy = true. but to invoke OnPropertyChanged you need to use the setter, thus IsBusy! Works now even if set in the worker thread. Put it back to ComputeResults!
Thread.Sleep(5000);
this.IsBusy = false;
}

Silverlight 5 Binding - Why isn't this working?

I can bind a combobox in the codebehind like this:
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
var combo = sender as ComboBox;
App.SchedulerVM = new ScheduleViewModel();
combo.DataContext = App.SchedulerVM;
combo.ItemsSource = App.SchedulerVM.Frequency;
}
This works - my combobox has the items from the Frequency List in the SchedulerVM object.
However, I don't want to do any of this in the codebehind. But the ways I've done this in WP7 before aren't working here. If I comment out the last line in the Loaded method above and try to set the ItemsSource in XAML, it doesn't work - nothing shows up:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
Frequency}" />
This doesn't work either:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
App.SchedulerVM.Frequency}" />
Nor this:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
SchedulerVM.Frequency}" />
Ideally, the DataContext wouldn't have to be explicitly set in the codebehind for this control either, it would be inherited from the LayoutRoot, where I've set it in the codebehind. But that's step 2 of my troubleshooting here.
What am I doing wrong? '
Thanks!
Edit
The ScheduleViewModel looks like this:
namespace SchedulerUI.ViewModels
{
public class ScheduleViewModel : INotifyPropertyChanged
{
//private properties
private Schedule _thisSchedule;
//public properties
public Schedule ThisSchedule
{
get { return _thisSchedule; }
set
{
if (value != _thisSchedule)
{
NotifyPropertyChanged("ThisSchedule");
}
_thisSchedule = value;
}
}
public List<string> Frequency = new List<string>();
public string Test;
//constructors
public ScheduleViewModel()
{
Frequency.AddRange(new string[] { "Daily", "Weekly", "Monthly" });
Test = "This is only a test.";
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here's the entire XAML:
<UserControl x:Class="SchedulerUI.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<ComboBox Height="23" HorizontalAlignment="Left" Margin="34,41,0,0" Name="comboBox1" Loaded ="comboBox1_Loaded" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Frequency}" />
<TextBox BorderBrush="Black" HorizontalAlignment="Left" Margin="34,41,0,0" Width="100" Height="100" DataContext="LayoutRoot.DataContext" Text="{Binding Test}" />
</Grid>
</UserControl>
Here's the entire codebehind:
namespace SchedulerUI
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
App.SchedulerVM = new ScheduleViewModel();
comboBox1.DataContext = App.SchedulerVM;
List<string> testlist = App.SchedulerVM.Frequency;
string teststring = App.SchedulerVM.Test;
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
//App.SchedulerVM = new ScheduleViewModel();
//var root = sender as Grid;
//if (root != null)
//{
// root.DataContext = App.SchedulerVM;
//}
}
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
//var combo = sender as ComboBox;
//App.SchedulerVM = new ScheduleViewModel();
//combo.DataContext = App.SchedulerVM;
//combo.ItemsSource = App.SchedulerVM.Frequency;
}
}
}
You binding is not working, because:
when you set ItemsSource in XAML its get executed first and it tries to bind the wrong/empty DataContext
then the Loaded event is raised which will set the correct DataContext but your already existing binding won't be refreshed automatically.
If you have to set the DataContext in the codebehind do it in your views constructor:
public YourView()
{
InitializeComponent();
combo.DataContext = App.SchedulerVM;
}
Then the following binding should work:
<ComboBox Name="comboBox1" ItemsSource="{Binding Frequency}" />
The databinding in WPF/Silverlight needs public properties. Currently Frequency is a public field on your viewmodel change it to a property and everthing should work:
private List<string> frequency = new List<string>();
public List<string> Frequency { get { return frequency; } set { frequency = value; }
And that is why it worked your initial loaded event because you didn't used databind there but you just set the combo.ItemsSource.

Speed of Color Resolution in XAML (Silverlight)

Is there anyway to speed-test which of the following resolve faster in XAML for colors:
Named Color: Orange
Hex: #ff6600
Shorthand Hex: #f60
Known Color: DarkOrange
This is not just a curiosity or an academic exercise. I have tons and tons of animations and colors that change colors many many times, on a large scale. I need to eek out ever bit of time-saving I can.
Looking for a way to test the above against each other for Silverlight. Any ideas?
I couldn't think of a definitive way to test this, but I put something together that may give you a hint. Effectively, I bound the color of a rectangle to a (string) SelectedColor property of a class that raised the PropertyChanged event every time it changed. Then I created four different List collections that had colors defined in all the different ways that you described, and in a background thread looped through them each some 10000 times, setting the SelectedColor property on each loop by dispatching to back to the UI thread. The complicated part was just keeping all the threads synchronized properly, and there's at least one hack in there where I loop every 20 ms until I saw that the previous task had finished. But it at least forces the UI thread to parse a color string, which is presumably what you're interested in.
At any rate, to the extent that this testing methodology is valid, it looks like using the "hex" notation is perhaps slightly faster than the others, though not by much. The average results on my machine after 10 test runs:
Hex: 4596 ms
Named: 4609 ms
Shorthand Hex: 5018 ms
Known Colors: 5065 ms
Just for reference, here's the code-behind:
public partial class MainPage : UserControl, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
namedColors.Add("Black");
namedColors.Add("Black");
namedColors.Add("Brown");
namedColors.Add("Cyan");
namedColors.Add("DarkGray");
namedColors.Add("Gray");
namedColors.Add("Green");
namedColors.Add("LightGray");
namedColors.Add("Magenta");
namedColors.Add("Orange");
hexColors.Add(Colors.Black.ToString());
hexColors.Add(Colors.Black.ToString());
hexColors.Add(Colors.Brown.ToString());
hexColors.Add(Colors.Cyan.ToString());
hexColors.Add(Colors.DarkGray.ToString());
hexColors.Add(Colors.Gray.ToString());
hexColors.Add(Colors.Green.ToString());
hexColors.Add(Colors.LightGray.ToString());
hexColors.Add(Colors.Magenta.ToString());
hexColors.Add(Colors.Orange.ToString());
knownColors.Add("LawnGreen");
knownColors.Add("LemonChiffon");
knownColors.Add("LightBlue");
knownColors.Add("LightCoral");
knownColors.Add("LightCyan");
knownColors.Add("LightGoldenrodYellow");
knownColors.Add("LightGray");
knownColors.Add("LightGreen");
knownColors.Add("LightPink");
knownColors.Add("LightSalmon");
shorthandHexColors.Add("#000");
shorthandHexColors.Add("#111");
shorthandHexColors.Add("#222");
shorthandHexColors.Add("#333");
shorthandHexColors.Add("#444");
shorthandHexColors.Add("#555");
shorthandHexColors.Add("#666");
shorthandHexColors.Add("#777");
shorthandHexColors.Add("#aaa");
shorthandHexColors.Add("#bbb");
LayoutRoot.DataContext = this;
}
private List<string> namedColors = new List<string>();
private List<string> hexColors = new List<string>();
private List<string> shorthandHexColors = new List<string>();
private List<string> knownColors = new List<string>();
private List<double> namedColorDurations = new List<double>();
private List<double> hexColorDurations = new List<double>();
private List<double> shorthandHexColorDurations = new List<double>();
private List<double> knownColorDurations = new List<double>();
private string selectedColor;
public string SelectedColor
{
get
{
return selectedColor;
}
set
{
if (selectedColor != value)
{
selectedColor = value;
RaisePropertyChanged("SelectedColor");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private bool isTesting = false;
private void testButton_Click(object sender, RoutedEventArgs e)
{
if (isTesting)
{
return;
}
else
{
isTesting = true;
}
ThreadPool.QueueUserWorkItem(o =>
{
AutoResetEvent resetEvent = new AutoResetEvent(false);
for (int i = 0; i < 10; i++)
{
TestColors(resetEvent, hexColors, lstHexColorDuration, hexColorDurations);
resetEvent.WaitOne();
TestColors(resetEvent, namedColors, lstNamedColorDuration, namedColorDurations);
resetEvent.WaitOne();
TestColors(resetEvent, shorthandHexColors, lstShorthandHexDuration, shorthandHexColorDurations);
resetEvent.WaitOne();
TestColors(resetEvent, knownColors, lstKnownColorDuration, knownColorDurations);
resetEvent.WaitOne();
}
Dispatcher.BeginInvoke(() =>
{
lstHexColorDuration.Items.Add(hexColorDurations.Average().ToString());
lstNamedColorDuration.Items.Add(namedColorDurations.Average().ToString());
lstShorthandHexDuration.Items.Add(shorthandHexColorDurations.Average().ToString());
lstKnownColorDuration.Items.Add(knownColorDurations.Average().ToString());
});
isTesting = false;
});
}
private int testsFinished = 0;
private void TestColors(AutoResetEvent resetEvent, List<string> colorList, ListBox resultListBox, List<double> results)
{
ThreadPool.QueueUserWorkItem(o =>
{
var start = DateTime.Now;
int testPasses = 10000;
testsFinished = 0;
for (int i = 0; i < testPasses; i++)
{
foreach (string color in colorList)
{
SetColor(color);
}
}
while (testsFinished < testPasses * colorList.Count)
{
Thread.Sleep(20);
}
DateTime finish = DateTime.Now;
results.Add((finish - start).TotalMilliseconds);
Dispatcher.BeginInvoke(() => resultListBox.Items.Add((DateTime.Now - start).ToString()));
resetEvent.Set();
});
}
private void SetColor(string color)
{
Dispatcher.BeginInvoke(() =>
{
SelectedColor = color;
Interlocked.Increment(ref testsFinished);
});
}
}
And here's the XAML proper:
<UserControl
x:Class="SilverlightScratch.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:local="clr-namespace:SilverlightScratch"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500">
<Grid x:Name="LayoutRoot" Background="White" >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Height="30" Width="100" x:Name="testButton" Content="Start Test" Click="testButton_Click" />
<Rectangle Height="30" Width="100" Fill="{Binding SelectedColor}" Grid.Row="2" />
<StackPanel Orientation="Horizontal" Grid.Row="1">
<StackPanel>
<TextBlock Text="Named Color Duration" />
<ListBox x:Name="lstNamedColorDuration" />
</StackPanel>
<StackPanel>
<TextBlock Text="Hex Color Duration" />
<ListBox x:Name="lstHexColorDuration" />
</StackPanel>
<StackPanel>
<TextBlock Text="Shorthand Hex Duration" />
<ListBox x:Name="lstShorthandHexDuration" />
</StackPanel>
<StackPanel>
<TextBlock Text="Known Colors Duration" />
<ListBox x:Name="lstKnownColorDuration" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>

Resources