Show busy indicator if long running action is on ui thread - c#

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

Related

How can I show the window in WPF without lost controls?

I have a program when I log in, I have to wait for loading database from server. So I created a "Loading..." window. After loading the database, this will automatically close the "Loading" window, the "Login" window and open the MainWindow.xaml.
But myprogram have a problem: when I use waitForm.show() in Login.xaml.cs, it runs good, but the controls on "Waiting" window like progressbar, textblock, it doesn't display.If I use waitForm.showdialog(), it will display progressbar and textblock control. But it won't automatically close. So my Mainwindow.xaml not open.
Is it possible to use show() but show the controls?
Sorry about my bad English. Thank you.
Waiting.xaml
<Window x:Class="TMO.Waiting"
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:TMO"
mc:Ignorable="d"
Title="Waiting" Height="100" Width="300" WindowStartupLocation="CenterScreen" WindowStyle="None">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ProgressBar Margin="10,20,10,5" Minimum="0" Maximum="100" Name="pbStatus" IsIndeterminate="True" Grid.Row="0"/>
<TextBlock Grid.Row="1" Margin="0,5,0,5" TextAlignment="Center" VerticalAlignment="Center" FontSize="20" FontWeight="Bold" >Loading...</TextBlock>
</Grid>
</Window>
Waiting.xaml.cs
using System.Windows;
namespace TMO
{
/// <summary>
/// Interaction logic for Waiting.xaml
/// </summary>
public partial class Waiting : Window
{
public static MessageBoxResult result;
public Waiting()
{
InitializeComponent();
}
public Waiting(Window parent1)
{
InitializeComponent();
}
public void CloseWaiting()
{
DialogResult = true;
this.Close();
}
}
}
WaitFunc.cs
using System.Threading;
using System.Windows;
namespace TMO
{
public class WaitFunc
{
Waiting wait;
Thread loadthread;
public void show()
{
loadthread = new Thread(new ThreadStart(LoadingProcess));
loadthread.Start();
}
public void show(Window parent)
{
loadthread=new Thread(new ParameterizedThreadStart(LoadingProcess));
loadthread.Start(parent);
}
public void Close()
{
if(wait!=null)
{
wait.Dispatcher.BeginInvoke(new System.Threading.ThreadStart(wait.CloseWaiting));
wait=null;
loadthread=null;
}
}
public void LoadingProcess()
{
wait=new Waiting();
wait.ShowDialog();
}
public void LoadingProcess(object parent)
{
Window parent1 = parent as Window;
wait=new Waiting(parent1);
wait.ShowDialog();
}
}
}
Login.xaml.cs
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
this.Hide();
Waiting waitForm = new Waiting();
waitForm.Show(); //If I use waitForm.showdialog() it will display all control
Thread.Sleep(500);
MainWindow main = new MainWindow(txtUserName.Text);
this.Hide();
waitForm.Close();
main.Show();
this.Close();
}
You should manage all windows of the application launch sequence from the entry point of your application. Therefore, I recommend to move the logic to start the MainWindow from the Login.xaml.cs to the App.xaml.cs.
Furthermore, to create a UI thread that can show controls and allows input handling you must explicitly configure the thread to be a STA thread and additionally start the Dispatcher message loop.
Note, if you use async APIs and background threads when necessary (use Task.Run and not Thread), then you don't need an extra UI thread only to show the splash screen.
You should add a LoginSuccessful and a LoginFailed event to the LoginWindow.
Your flow could be implemented like this:
App.xaml
<Application Startup="App_OnStartup" />
App.xaml.cs
public partial class App : Application
{
private Window SplashScreen { get; set; }
private void App_OnStartup(object sender, StartupEventArgs e)
{
ShutdownMode = ShutdownMode.OnExplicitShutdown;
var loginScreen = new LoginWindow();
loginScreen.LoginSuccessful += RunApplication_OnLoginSuccessful;
loginScreen.LoginFailed += ShutdownApplication_OnLoginFailed;
loginScreen.Show();
}
private void RunApplication_OnLoginSuccessful(object sender, EventArgs e)
{
var newUiThread = new Thread(new ThreadStart(ShowSplashScreen))
{
ApartmentState = ApartmentState.STA,
IsBackground = false,
};
newUiThread.Start();
var mainWindow = new MainWindow();
mainWindow.Loaded += CloseSplashScreen_OnMainWindowLoaded;
mainWindow.Closed += ShutdownApplication_OnMainWindowClosed;
// TODO::Initialize application
mainWindow.Show();
}
private void ShowSplashScreen()
{
this.SplashScreen = new Window() { Content = "SplashScreen" };
this.SplashScreen.Show();
// Start event queue
Dispatcher.Run();
}
private void CloseSplashScreen_OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
this.SplashScreen.Dispatcher.InvokeAsync(() =>
{
this.SplashScreen.Close();
this.SplashScreen.Dispatcher.InvokeShutdown();
});
}
private void ShutdownApplication_OnMainWindowClosed(object sender, EventArgs e) => Shutdown();
private void ShutdownApplication_OnLoginFailed(object sender, EventArgs e) => Shutdown();

Update textbox in realtime in wpf giving no error with no output

I have tried multiple methods to try to get this to update in real-time, as I am attempting to make a simple GUI-based stopwatch. As of right now I am NOT trying to make it look pretty at all, I am just trying to get the program to run. I have included the methods that I have attempted to use in order to get the TextBlock to update. The last one that I attempted was the this.Dispatcher.Invoke( new Action(() => ... method. The other way I attempted was using the async-await method which I have included in the code in comments.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private /*async*/ void start_Click(object sender, RoutedEventArgs e)
{
// await Task.Run(() =>
{
this.Dispatcher.Invoke( new Action(() =>
{
Stopwatch Timer = new Stopwatch();
Timer.Start();
TimeSpan goneby = Timer.Elapsed;
string time = String.Format("{0:00}:{1:00}.{2:00}",
goneby.Minutes, goneby.Seconds,
goneby.Milliseconds / 10);
TextBlock textBlock = new TextBlock();
textBlock.Width = 100;
textBlock.Height = 50;
textBlock.HorizontalAlignment = HorizontalAlignment.Center;
textBlock.VerticalAlignment = VerticalAlignment.Top;
textBlock.Text = time;
}));
// });
}
}
}
}
Here is the XAML just in case it is needed to solve this:
<Window x:Name="window1" x:Class="WpfApp3.Window1"
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:WpfApp3"
mc:Ignorable="d"
Title="New Window" Height="300" Width="300">
<Grid>
<Button x:Name="start" Content="Start" HorizontalAlignment="Left" VerticalAlignment="Top" Width="213" Margin="38,181,0,0" Height="50" Click="start_Click"/>
</Grid>
</Window>
try this
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Name="lblTime" FontSize="50" Margin="149,50,-149.333,-50.333" />
<Button x:Name="start" Content="Start" HorizontalAlignment="Left" VerticalAlignment="Top" Width="213" Margin="149,166,0,0" Height="50" Click="start_Click"/>
</Grid>
</Window>
using System;
using System.Timers;
using System.Windows;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Timer timer;
TimeSpan time;
public MainWindow()
{
InitializeComponent();
time = new TimeSpan(0);
timer = new Timer();
timer.Interval = 100;
timer.Elapsed += timer_Elapsed;
}
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
time += new TimeSpan(0, 0, 0, 0, 100);
this.Dispatcher.Invoke(() => {
lblTime.Text = time.ToString(#"hh\:mm\:ss\:ff");
});
}
private void start_Click(object sender, RoutedEventArgs e)
{
if (!timer.Enabled)
{
timer.Start();
start.Content = "Stop";
}
else
{
timer.Stop();
start.Content = "Start";
}
}
}
}
Using async/await is a neat solution. Note that there is no synchronization problem or locks required because all code is running on the WPF thread.
using System;
using System.Timers;
using System.Windows;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
bool running = false; // not running
TimeSpan updateTime = TimeSpan.FromSeconds(0.1);
public MainWindow()
{
InitializeComponent();
}
// Note that async void should only be used for event handlers
private async void start_Click(object sender, RoutedEventArgs e)
{
if (!running)
{
start.Content = "Stop";
var watch = StopWatch.StartNew();
while(running){
var timeSpan = watch.Elapsed;
var message =
$"Time: {timeSpan.Hours}h {timeSpan.Minutes}m " +
"{timeSpan.Seconds}s {timeSpan.Milliseconds}ms";
lblTime.Text = message
// async sleep for a bit before updating the text again
await Task.Delay(updateTime);
}
}
else
{
running = false;
start.Content = "Start";
}
}
}
}
My Version of a simple Stopwatch:
MainWindow.xaml
<Window x:Class="Test.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:Test"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:StopwatchManager x:Key="stopwatchManager" />
</Window.Resources>
<Grid>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="{Binding Source={StaticResource stopwatchManager}, Path=Stopwatch1.Duration, Mode=OneWay}" VerticalAlignment="Top" Height="27" Width="188"/>
<Button x:Name="btnStart" Content="Start" HorizontalAlignment="Left" Margin="10,42,0,0" VerticalAlignment="Top" Width="53" Click="btnStart_Click"/>
<Button x:Name="btnStop" Content="Stop" HorizontalAlignment="Left" Margin="68,42,0,0" VerticalAlignment="Top" Width="53" Click="btnStop_Click"/>
</Grid>
</Window>
MainWinwdow.xaml.cs
using System;
using System.ComponentModel;
using System.Timers;
using System.Windows;
namespace Test
{
public partial class MainWindow : Window
{
StopwatchManager stopwatchManager = new StopwatchManager();
public MainWindow()
{ InitializeComponent(); }
private void btnStart_Click(object sender, RoutedEventArgs e)
{ stopwatchManager.Stopwatch1.Start(); }
private void btnStop_Click(object sender, RoutedEventArgs e)
{ /*SaveDuration();*/ stopwatchManager.Stopwatch1.Stop(); }
}
public class StopwatchManager
{
public Stopwatch Stopwatch1 { get { return _stopwatch1; } set { _stopwatch1 = value; } }
static Stopwatch _stopwatch1 = new Stopwatch();
}
public class Stopwatch : INotifyPropertyChanged
{
private Timer timer = new Timer(100);
public event PropertyChangedEventHandler PropertyChanged;
public DateTime StartTime { get; set; } = DateTime.Now;
public double Interval
{
get { return timer.Interval; }
set { timer.Interval = value; }
}
public TimeSpan Duration { get { return DateTime.Now - StartTime; } }
public Stopwatch()
{ timer.Elapsed += timer_Elapsed; }
public void Start()
{ StartTime = DateTime.Now; timer.Start(); }
public void Stop()
{ /*SaveDuration();*/ timer.Stop(); }
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Duration")); }
}
}
ToDo:
-Change namespace ("Test")
-Implement "SaveDuration()"
-Set UI-Updatetime => new Timer(100); //=0.1 sec
-To get multible StopWatches duplicate or make it a List
public Stopwatch Stopwatch1 { get { return _stopwatch1; } set { _stopwatch1 = value; } }
static Stopwatch _stopwatch1 = new Stopwatch();
Source on GIT-Hub
You need to create Timer to update your Textblock:
Try this:
public partial class Window1: Window
{
DispatcherTimer dt = new DispatcherTimer();
Stopwatch sw = new Stopwatch();
string currentTime = string.Empty;
public MainWindow()
{
InitializeComponent();
dt.Tick += new EventHandler(dt_Tick);
dt.Interval = new TimeSpan(0, 0, 0, 0, 1);
}
void dt_Tick(object sender, EventArgs e)
{
if (sw.IsRunning)
{
TimeSpan ts = sw.Elapsed;
currentTime = String.Format("{0:00}:{1:00}:{2:00}",
ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
YourtextBlock.Text = currentTime;
}
}
private void startbtn_Click(object sender, RoutedEventArgs e)
{
sw.Start();
dt.Start();
}
}
Original Source:
Here

Integrating Context-Sensitive help in WPF application following MVVM

I am trying to implement Help functionality for my wpf application which is following the MVVM pattern. I have my help file present, which contains many pages according to the application. Now I need to integrate this into my application.
Here are my requirements:
Pressing F1 opens a certain page in the help file depending on the view model. For this, I guess, I need to bind the F1 command to my view model. How do we bind keys in views?
Pressing F1 on a text field opens help for that text field. I think it will be the same as requirement 1. But the problem here is how will I know that a certain text field, button, or radio button is selected?
Listen for the key in the view (or a base class of the view) and call execute on a HelpCommand on the DataContext.
Pass the control that has focus (or its id, or tag, ...) as an argument to the HelpCommand.
Alternative way to find the focussed control by using the FocusManager
Here is an example:
ContextHelp C#:
public static class ContextHelp
{
public static readonly DependencyProperty KeywordProperty =
DependencyProperty.RegisterAttached(
"Keyword",
typeof(string),
typeof(ContextHelp));
public static void SetKeyword(UIElement target, string value)
{
target.SetValue(KeywordProperty, value);
}
public static string GetKeyword(UIElement target)
{
return (string)target.GetValue(KeywordProperty);
}
}
ViewBase:
public abstract class ViewBase : UserControl
{
public ViewBase()
{
this.KeyUp += ViewBase_KeyUp;
this.GotFocus += ViewBase_GotFocus;
}
void ViewBase_GotFocus(object sender, RoutedEventArgs e)
{
FocusManager.SetIsFocusScope(this, true);
}
void ViewBase_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.F1)
{
var viewModel = this.DataContext as ViewModelBase;
if (viewModel != null)
{
var helpTopic = "Index";
var focusedElement =
FocusManager.GetFocusedElement(this) as FrameworkElement;
if (focusedElement != null)
{
var keyword = ContextHelp.GetKeyword(focusedElement);
if (!String.IsNullOrWhiteSpace(keyword))
{
helpTopic = keyword;
}
}
viewModel.HelpCommand.Execute(helpTopic);
}
}
}
}
ViewModelBase:
public abstract class ViewModelBase: INotifyPropertyChanged
{
public ICommand HelpCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName="")
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
AViewModel:
class AViewModel : ViewModelBase
{
public AViewModel()
{
HelpCommand = new RelayCommand(HelpCommandExecuted, (p)=>true);
}
private void HelpCommandExecuted(object parameter)
{
var topic = parameter as string;
if (!String.IsNullOrWhiteSpace(topic))
{
HelpText = String.Format("Information on the interesting topic: {0}.", topic);
}
}
private string _helpText;
public string HelpText
{
get { return _helpText; }
private set
{
if (_helpText != value)
{
_helpText = value;
OnPropertyChanged();
}
}
}
}
AView C#:
public partial class AView : ViewBase
{
public AView()
{
InitializeComponent();
}
}
AView XAML:
<local:ViewBase x:Class="WpfApplication2.AView"
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:WpfApplication2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Label Content="{Binding HelpText}" Margin="10,254,10,0" VerticalAlignment="Top" Height="36"/>
<Button local:ContextHelp.Keyword="Button Info" Content="Button" HorizontalAlignment="Left" Margin="192,32,0,0" VerticalAlignment="Top" Width="75"/>
<TextBox local:ContextHelp.Keyword="TextBox Info" HorizontalAlignment="Left" Height="23" Margin="29,32,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<CheckBox local:ContextHelp.Keyword="CheckBox Info" Content="CheckBox" HorizontalAlignment="Left" Margin="29,80,0,0" VerticalAlignment="Top"/>
<ComboBox local:ContextHelp.Keyword="ComboBox Info" HorizontalAlignment="Left" Margin="138,80,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
</local:ViewBase>
MainWindow XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2" x:Class="WpfApplication2.MainWindow"
Title="MainWindow" Height="700" Width="500">
<Grid x:Name="ViewPlaceholder">
</Grid>
</Window>
MainWindow C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var view = new AView();
var viewModel = new AViewModel();
view.DataContext = viewModel;
ViewPlaceholder.Children.Clear();
ViewPlaceholder.Children.Add(view);
}
}

Scroller-like animation on a Label in WPF

I have a simple app where after clicking a button the value of a label is updated every second.I'm doing this as a POC for a progress bar control that I want to develop.
I would like to know if there is a way to apply some kind of scroller animation to the label which will:
1) When the content of a label is updated it will scroll the new value from the top and the old one will be scrolled down and disappear from view(Hope this makes sence).
I know that this could probably be achieved with some kind of animation but I couldn't find any helpful examples on the web if anyone knows how this can be done please share your expertise:
View:
<Window x:Class="WpfApplication1.ScrollerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Scroller" DataContext="{StaticResource scrollerVM}" Height="150" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Messages}" Width="200" Height="50" BorderThickness="0" VerticalAlignment="Top" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button Width="70" Height="24" Content="Add new" Command="{Binding AddNew}" HorizontalAlignment="Left" Margin="0,56,0,30" />
</Grid>
</Window>
View model:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Threading;
namespace WpfApplication1.Scroller
{
public class Message
{
public Message(string _text)
{
text = _text;
}
private string text;
public string Text
{
get { return text; }
set {text = value;}
}
}
public class ScrollerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DelegateCommand AddNew { get; protected set; }
ObservableCollection<Message> _messages = new ObservableCollection<Message>();
public ObservableCollection<Message> Messages
{
get { return _messages; }
set
{
_messages = value;
OnPropertyChanged("Messages");
}
}
public ScrollerViewModel()
{
AddNew = new DelegateCommand(Add);
}
private void Add(object parameter)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += new System.EventHandler(timer_Tick);
timer.Interval = new System.TimeSpan(0, 0, 1);
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
Messages.Clear();
Messages.Add(new Message(DateTime.Now.ToString("ss")));
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
More comprehensive/different examples here.
The following will result in a basic vertical marquee (scrolling text block).
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" Loaded="Window_Loaded">
<Canvas Name="canvas1" >
<TextBlock Name="textBlock1">Hello</TextBlock>
</Canvas>
</Window>
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BeginAnimation()
{
DoubleAnimation doubleAnimation = new DoubleAnimation();
doubleAnimation.From = -textBlock1.ActualHeight;
doubleAnimation.To = canvas1.ActualHeight;
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(3));
textBlock1.BeginAnimation(Canvas.TopProperty, doubleAnimation);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
BeginAnimation();
}
}
First, you'll want "smooth scrolling" on the ListBox:
ScrollViewer.CanContentScroll="False"
Then, you could create a custom Attached Property to specify the vertical offset you want to scroll. Then create a custom Behavior that hooks up to the ListBox's ItemsSource's "ItemsSourceChanged" event, which would fire off an animation that you can define inside the behavior. That should at least be a start. I'm not sure what the specific animation would be...some DoubleAnimation using a calculation of your offset plus new item's height.

Wpf Label not being updated when using Thread.Sleep()

I have a Label the Content of which I would like to update after each second,after 3 seconds I only see the last string "Step 3..." What am I doing wrong and is there another way to achieve this if for some reason I cannot use Thread.Sleep():
View:
<Window x:Class="WpfApplication1.ScrollerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Scroller" DataContext="{StaticResource scrollerVM}" Height="150" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Messages}" Width="200" Height="50" BorderThickness="0" VerticalAlignment="Top" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button Width="70" Height="24" Content="Add new" Command="{Binding AddNew}" HorizontalAlignment="Left" Margin="0,56,0,30" />
</Grid>
</Window>
View model:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
namespace WpfApplication1.Scroller
{
public class Message
{
public Message(string _text)
{
text = _text;
}
private string text;
public string Text
{
get { return text; }
set {text = value;}
}
}
public class ScrollerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DelegateCommand AddNew { get; protected set; }
ObservableCollection<Message> _messages = new ObservableCollection<Message>();
public ObservableCollection<Message> Messages
{
get { return _messages; }
set
{
_messages = value;
OnPropertyChanged("Messages");
}
}
public ScrollerViewModel()
{
AddNew = new DelegateCommand(Add);
}
private void Add(object parameter)
{
UpdateProgress("Step 1...");
UpdateProgress("Step 2...");
UpdateProgress("Step 3...");
}
private void UpdateProgress(string step)
{
Messages.Clear();
Messages.Add(new Message(step));
Thread.Sleep(1000);
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
It is because you are sleeping in the UI thread. The UI won't have a chance to update until Add is finished. You can use a BackgroundWorker with ReportProgress to achieve what you want. Something like this:
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
worker.ReportProgress(1, "Step1");
Thread.Sleep(1000);
worker.ReportProgress(2, "Step2");
Thread.Sleep(1000);
worker.ReportProgress(3, "Step3");
};
worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
string step = (string)args.UserState;
Messages.Clear();
Messages.Add(new Message(step));
};
worker.RunWorkerAsync();
The UI thread won't be occupied while DoWork is executed, but the code in ProgressChanged will be performed on the UI thread.
You should NEVER call Thread.Sleep when you´re on the UI Thread. This will block the Dispatcher and no rendering. binding updates etc. will occur during this time.
If you want to wait between your UpdateProgress() calls, use a DispatcherTimer.
You are clearing the messages before adding the new one.
private void UpdateProgress(string step)
{
Messages.Clear(); // <- Why?
Messages.Add(new Message(step));
Thread.Sleep(1000);
}

Categories

Resources