WPF Update grid visibility - c#

After some search on web I have set up this simple example:
PropertyChangedBase.cs
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action)(() => {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
UserViewModel.cs
public class UserViewModel : PropertyChangedBase
{
private Visibility _showUserWindow = Visibility.Collapsed;
public Visibility ShowUserWindow {
get { return _showUserWindow; }
set {
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}
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"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid Margin="43,28,247,129" Background="AliceBlue" Visibility="{Binding ShowUserWindow}"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="349,150,0,0" VerticalAlignment="Top" Width="75" PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
UserViewModel userViewModel;
public MainWindow() {
InitializeComponent();
userViewModel = new UserViewModel();
DataContext = userViewModel;
}
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
userViewModel.ShowUserWindow = Visibility.Visible;
Thread.Sleep(1000);
userViewModel.ShowUserWindow = Visibility.Collapsed;
}
}
Right now grid becomes collapsed after 1 sec, I would like to update UI before timer starts. What I am doing wrong?
Edit:
Thread.Sleep line immitates some work, that takes some time to complete.
Grid should become visible before work starts and show some info about that work and become collapsed after work is done.

Well, you should consider doing the Thread.Sleep(1000) operation on a separate thread, not on the UI thread. Check this for that.
Other than that, try to use yourGrid.UpdateLayout() method after setting its visibility to collapsed.
LE:
Most probably, his Thread.Sleep(1000) stands for something like a database operation, for example, something that takes time.
LE2: A BackgroundWorker will do the trick. Check this link!

If you are using .NET 4.5, you can use Task.Delay() to allow UI to update itself before starting the actual work:
private async void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
userViewModel.ShowUserWindow = Visibility.Visible;
await Task.Delay(1);
Thread.Sleep(1000);
userViewModel.ShowUserWindow = Visibility.Collapsed;
}
Please note that await Task.Delay(1); should be used even after you replace Thread.Sleep(1000) with actual work.
However this should be used only if your work can be done only in UI thread and cannot be moved to a background thread (for example, massive loading of UI elements). Otherwise the correct approach is to move the work to a background thread:
private async void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
userViewModel.ShowUserWindow = Visibility.Visible;
await Task.Start(() => {
Thread.Sleep(1000);
};
userViewModel.ShowUserWindow = Visibility.Collapsed;
}

Related

Show busy indicator if long running action is on ui thread

I have my xaml:
<Window x:Class="Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Height="768" Width="1024" WindowStartupLocation="CenterScreen"
Title="{Binding Path=DisplayName}">
<xctk:BusyIndicator x:Name="BusyIndicator" IsBusy="{Binding IsBusy, UpdateSourceTrigger=PropertyChanged}" >
<TreeView Style="{StaticResource TableSchemaTreeViewStyle}" ItemContainerStyle="{StaticResource SchemaTreeViewStyle}" Margin="0,15,0,0"
x:Name="TreeViewSchema"
TreeViewItem.Expanded="TreeViewSchema_OnExpanded"
TreeViewItem.Selected="TreeViewSchema_OnSelected"
Grid.Row="2"
ItemsSource="{Binding CurrentProject.Tables, Converter={StaticResource TreeViewSortingConverter}, ConverterParameter=FullTableName}">
</TreeView>
</xctk:BusyIndicator>
</Window>
And suppose I have long running task in code-behind which is performed on UI thread (long filtering of treeview, it can have more then 1000 tables and each table more than 100 columns in it).
Lets say I iterate and set tableTreeViewItem.Visibility = Visibility.Collapsed; for each item.
What I want: show BusyIndicator by setting it to true before this action:
BusyIndicator.IsBusy = true;.
The problem: both actions on UI thread and binding does not work as expected. I tried few things:
BusyIndicator.IsBusy = true;
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
tableTreeViewItem.Visibility = Visibility.Collapsed;
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler).ContinueWith(task => Dispatcher.Invoke(() =>
{
BusyIndicator.IsBusy = false;
}));
And using dispatcher:
BusyIndicator.IsBusy = true;
//long-running UI task
tableTreeViewItem.Visibility = Visibility.Collapsed;
BusyIndicator.IsBusy = false;
But it does not work, any ideas how to fix it?
PSS
I made some updates, I decided to grab all data and store which tree view item should be visible or hidden.
So I have class which stores table, visibility and visible columns for filter method
class TreeViewItemVisibilityTableContainer
{
private TreeViewItem _treeViewItem;
private TableModel _table;
private Visibility _visibility;
private List<ColumnModel> _visibleColumns;
public TableModel Table
{
get { return _table; }
set { _table = value; }
}
public TreeViewItem TreeViewItem
{
get { return _treeViewItem; }
set { _treeViewItem = value; }
}
public Visibility Visibility
{
get { return _visibility; }
set { _visibility = value; }
}
public List<ColumnModel> VisibleColumns
{
get { return _visibleColumns; }
set { _visibleColumns = value; }
}
}
And now I can filter all this staff directly on UI thread:
System.Action filterTreeViewItemsVisibility = () => Dispatcher.Invoke(() =>
{
foreach (var item in itemsToFilter)
{
item.TreeViewItem.Visibility = item.Visibility;
var capturedItemForClosure = item;
if (item.Visibility == Visibility.Visible)
{
if (item.VisibleColumns.Any())
{
item.TreeViewItem.Items.Filter = item.TreeViewItem.Items.Filter =
treeViewItem =>
capturedItemForClosure.VisibleColumns.Any(
columnModel => columnModel.Equals(treeViewItem));
}
else
{
item.TreeViewItem.Visibility = Visibility.Collapsed;
}
}
}
});
IoC.Get<IBusyIndicatorHelper>().PerformLongrunningAction(filterTreeViewItemsVisibility, IoC.Get<IShellViewModel>());
But it is still super slow
Here is my solution for the busy indicator.
There are few components to show the solutions
BusyIndicator User Control
AbortableBackgroundWorker
MainWindow as application window
BusyIndicator user control
BusyIndicator.xaml - pretty simply
<UserControl x:Class="BusyIndicatorExample.BusyInidicator"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="Collapsed">
<Grid Background="#BFFFFFFF" >
<TextBlock Text="Loading data..." HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF2C2C2C" FontSize="16" FontWeight="Bold" />
</Grid>
</UserControl>
BusyIndicator.xaml.cs
using System;
using System.Windows.Controls;
namespace BusyIndicatorExample
{
/// <summary>
/// Interaction logic for BusyInidcator.xaml
/// </summary>
public partial class BusyInidicator : UserControl
{
public BusyInidicator()
{
InitializeComponent();
}
Method for showing indicator
public void Start()
{
this.Dispatcher.Invoke(new Action(delegate()
{
this.Visibility = System.Windows.Visibility.Visible;
}), System.Windows.Threading.DispatcherPriority.Normal);
}
Method for hiding indicator
public void Stop()
{
this.Dispatcher.Invoke(new Action(delegate()
{
this.Visibility = System.Windows.Visibility.Collapsed;
}), System.Windows.Threading.DispatcherPriority.Normal);
}
}
}
AbortableBackgroundWorker for simulation non UI task
using System;
using System.ComponentModel;
using System.Threading;
namespace BusyIndicatorExample
{
/// <summary>
/// Abortable background worker
/// </summary>
public class AbortableBackgroundWorker : BackgroundWorker
{
//Internal Thread
private Thread workerThread;
protected override void OnDoWork(DoWorkEventArgs e)
{
try
{
base.OnDoWork(e);
}
catch (ThreadAbortException)
{
e.Cancel = true; //We must set Cancel property to true!
Thread.ResetAbort(); //Prevents ThreadAbortException propagation
}
}
public void Abort()
{
if (workerThread != null)
{
workerThread.Abort();
workerThread = null;
}
}
}
}
Finally, MainWindow where the process is simulated
MainWindow.xaml
<Window x:Class="BusyIndicatorExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BusyIndicatorExample"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Start Data Loading" HorizontalAlignment="Left" Margin="63,42,0,0" VerticalAlignment="Top" Width="125" Height="28" Click="Button_Click"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="63,87,0,0" TextWrapping="Wrap" Text="{Binding DataString}" VerticalAlignment="Top" Width="412"/>
<local:BusyInidicator x:Name="busyIndicator" HorizontalAlignment="Left" Height="100" Margin="177,140,0,0" VerticalAlignment="Top" Width="300"/>
</Grid>
</Window>
MainWindow.xaml.cs - here is the application code
using System.ComponentModel;
using System.Windows;
namespace BusyIndicatorExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private AbortableBackgroundWorker _worker;
Constructor and public property binded to the textbox
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private string _dataString = "No Data";
public string DataString
{
get { return _dataString; }
set {
if (_dataString != value)
{
_dataString = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("DataString"));
}
}
}
Button click event - initialize BackgroundWorker and starts it
private void Button_Click(object sender, RoutedEventArgs e)
{
if(_worker == null)
{
_worker = new AbortableBackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += _worker_DoWork;
_worker.RunWorkerCompleted += _worker_RunWorkerCompleted;
}
if (!_worker.IsBusy)
_worker.RunWorkerAsync();
}
BackgroundWorker event handlers
RunWorkerCompleted update data string and hide indicator.
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DataString = "Data has been loaded";
busyIndicator.Stop();
}
DoWork shows indicator and put to sleep thread for 5 sec.
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
DataString = "No Data";
busyIndicator.Start();
System.Threading.Thread.Sleep(5000);
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Hope this helps. Modify code as you need to fit to your scenario
Full example project code can be downloaded here

Windows Phone update binding from background thread

In my Windows Phone 8 Application I have Listbox below
<ListBox x:Name="ListBox1" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding snippet.DownloadPercentage}"
TextWrapping="Wrap"
FontFamily="Portable User Interface"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
I am downloading file asyn and would like to give progress percentage to UI like below;
but it does not update UI. It shows always 0 which is initial int value. If I access DownloadPercentage property at main thread
the it updates with no problem.
private void ClientOnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs downloadProgressChangedEventArgs)
{
// not working
item.snippet.DownloadPercentage = progressPercentage;
// not working
Dispatcher.BeginInvoke(delegate {
item.snippet.DownloadPercentage = progressPercentage;
});
// not working
ProgressChangedEventHandler workerProgressChanged = delegate {
item.snippet.DownloadPercentage = progressPercentage;
};
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += workerProgressChanged;
worker.ReportProgress(progressPercentage);
// WORKING!
#region ProgressIndicator
_progressIndicator.Text = string.Format("Downloading ({0}%) {1}", progressPercentage, item.snippet.Title);
_progressIndicator.IsVisible = true;
_progressIndicator.IsIndeterminate = true;
SystemTray.SetProgressIndicator(this, _progressIndicator);
#endregion
}
what can I do?
Solution;
after #DecadeMoon's hint I had to implement INotifyPropertyChanged over Snippet class.
public class Snippet : INotifyPropertyChanged
{
[JsonProperty("downloadPercentage")]
public int DownloadPercentage
{
get { return _downloadPercentage; }
set
{
_downloadPercentage = value;
RaisePropertyChanged("DownloadPercentage");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The object whose property you have bound to in the view must implement INotifyPropertyChanged. In your case, you're binding to snippet.DownloadPercentage, therefore the snippet class must implement INotifyPropertyChanged and must raise the PropertyChanged event in the setter of the DownloadPercentage property.
You must also make sure that you only modify the DownloadPercentage property from the UI thread, otherwise you'll get an exception if modified from another thread. This is generally done by using the dispatcher:
Dispatcher.BeginInvoke(() =>
{
item.snippet.DownloadPercentage = progressPercentage;
});

Updating dynamically the content property of a label with the WPF user model

A simple WPF window with a label on it:
<Window x:Name="MainWindow1" x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:A="clr-namespace:WpfApplication1"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="384" Width="669.114" Icon="alarmclock.ico" Closing="OnClosing" Loaded="OnLoaded" >
<Grid x:Name="Grid1">
<Label x:Name="Label1" Content="{Binding AlarmStatus}" HorizontalAlignment="Left" Margin="10,314,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
Wire up an object so that the object can be bound to the labels content property. Yes I see the text Off on the window:
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
public class UserViewModel : PropertyChangedBase
{
private string _alarmStatus = "Off";
public string AlarmStatus
{
get { return _alarmStatus; }
set
{
_alarmStatus = value;
OnPropertyChanged("AlarmStatus"); //This is important!!!
}
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
I thought I would be able to change the value of the label like this:
private void OnLoaded(object sender, RoutedEventArgs e)
{
//Im setting a value but the UI does not change. The label still says Off
UserViewModel aaaa = new UserViewModel();
aaaa.AlarmStatus = "On";
}
You're creating a new instance of the ViewModel, but the View has no "connection" to that.
the aaaa instance dies (goes out of scope and is de-referenced) immediately after the OnLoaded() method finishes executing, and is garbage collected a moment later.
What you need is to grab the actual instance of the ViewModel that's currently being used by the Window:
private void OnLoaded(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as UserViewModel;
viewModel.AlarmStatus = "On";
}
I suggest you read Rachel's Article to better understand the concept of DataContext in WPF.

When does binding actually happen

On a popup window I have a checkbox.IsChecked bound to a model, but I want to check its state from the xaml code behind when the window is displayed. When checking the checkbox by name in the code behind from the windows loaded event it is not set yet. There are some UI specific things that I need to do and that is why I need to know what the checkboxes value is when the window opens and i cannot perform this from the model that the checkbox.IsChecked is bound to.
The property on the model is set long before the popup window is opened, so it is not an issue of the binding not being there. I figured that once the Loaded event fires the window would be ready to use bindings and all, but this does not seem to be the case.
Xaml:
<RefinedRibbonControls:RefinedRibbonGroup Header="Show Graphs">
<StackPanel x:Name="panelVisibilities">
<CheckBox Content="Show/hide" x:Name="myCheckBox"
IsChecked="{Binding Path=Processor.Model.IsItemVisible}"
Click="GraphVisibilityClickEvent"
HorizontalAlignment="Left"/>
...etc
Property on model:
public bool IsItemVisible
{
get { return _isItemVisible ; }
set
{
if (_isItemVisible != value)
{
_isItemVisible = value;
_propertyChanger.FirePropertyChanged(this, m => m.IsItemVisible);
}
}
}
Event in Xaml codebehind:
private void WindowLoadedEvent(object sender, RoutedEventArgs e)
{
if(myCheckBox.IsChecked.Value)
{
// Do UI Related Stuff
}
}
The binding works fine and the values show up when the window is displayed, the problem is I cannot get the value of the binding in the window loaded event.
Edit: Possible solution I have found but I am not sure if its the best way.
I called the following method from the constructor on the xaml code behind.
private void SetupInitialVisibility()
{
//Fire after everything is loaded.
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
IEnumerable<CheckBox> elements = this.panelVisibilities.Children.OfType<CheckBox>().ToList();
foreach (CheckBox checkBox in elements)
{
if (checkBox.IsChecked != null && checkBox.IsChecked.Value == false)
{
//Do work
}
}
}));
}
found at: https://stackoverflow.com/a/1746975/1253746
Data binding is not done synchronously, but it is delayed. Check this msdn page on dispatcher priorities. It is done at a lower priority than normal window messages, but before rendering.
You could invoke a method on yourself with a lower priority than is defined for databinding, in this method you should be able to safely read the data bound value.
I would still find this ugly. I'd rather subscribe directly to PropertyChanged and check for this property, or even better, rewrite your "UI related code" as a data binding.
P.S. If you start consuming events, be sure to unsubscribe, or you might get memory leaks.
DataBinding should precede the Loaded event I think.
When and how do you set your DataContext? And you are positive that the viewmodel property is already set?
The following works, try to align your code with this if possible.
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:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<CheckBox x:Name="myCheckBox" IsChecked="{Binding IsItemVisible}" />
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if (myCheckBox.IsChecked.Value)
{
//...
}
}
}
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isItemVisible;
public bool IsItemVisible { get { return isItemVisible; } set { isItemVisible = value; OnPropertyChanged("IsItemVisible"); } }
public ViewModel()
{
this.IsItemVisible = true;
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

WPF - runtime updated binding question

I'm quite a novice with C# and a complete newbie regarding WPF. Probably it's a very basic question and piece of cake for the pros. Please bear with me.
I need to display a dynamic textblock with text changed in run time without additional triggers such as button clicks and so. For some reason (my insufficient understanding of the concept obviously) textblock stays empty.
Xaml is as simple as it can be:
<Window x:Class="WpfApplication1.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" Loaded="Window_Loaded">
<Grid>
<TextBlock Text="{Binding Path=Name}"/>
</Grid>
</Window>
And the code behind it simplified as well:
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Client client = new Client();
client.Name = "Michael";
Thread.Sleep(1000);
client.Name = "Johnson";
}
}
public class Client : INotifyPropertyChanged
{
private string name = "The name is:";
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return this.name;
}
set
{
if (this.name == value)
return;
this.name = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, e);
}
}
}
Thanks in advance,
Ceres
In order for the binding to work you need to set the DataContext of the window to the object you want to bind to, in this instance the client object.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Client client = new Client();
// Set client as the DataContext.
DataContext = client;
client.Name = "Michael";
Thread.Sleep(1000);
client.Name = "Johnson";
}
This should cause the TextBox to successfully update.
Just to point out that using Thread.Sleep() in the loaded event causes the program to hang for a second on startup, a better idea would be to use the WPF DispatcherTimer to create the 1 second delay.
Hope that helps!

Categories

Resources