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

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);
}

Related

In WinUI3, how do I know when the Window is loaded and visible to the user?

In WinUI3, I am wanting to provide updates to the user of a Window during the loading of resources. Each time I am loading a resource, I would like to set the text of a TextBlock. I have tried setting the text directly, performing two way data binding and using INotifyPropertyChanged. But for the life of me, I cannot do something so simple as update the UI. And yes, I have searched high and low on the web, and nothing has worked.
Please provide me with a simple c# and xaml example that updates a textblock in realtime as I am loading resources. Thank you.
Here is what I've tried.
XAML:
<StackPanel x:Name="LoadingStackPanel" >
<ProgressRing x:Name="LoadingProgressRing" IsActive="True" IsHitTestVisible="True" />
<TextBlock x:Name="ProgressTextBlock" Text="{x:Bind Path=GetData, Mode=TwoWay}" />
</StackPanel>
Method 1:
public event PropertyChangedEventHandler PropertyChanged;
private string _data = "Loading...";
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public string GetData
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("GetData");
}
}
And then setting the GetData property.
Method 2:
ProgressTextBlock.Text = "Loading resource ...";
Method 3:
DispatcherQueue.TryEnqueue(() => {
ProgressTextBlock.Text = "Loading resource ...";
});
Method 4:
DispatcherQueue.TryEnqueue(() => {
GetData = "Loading resource ...";
});
I managed to accomplish what I need to do, and so I am posting my answer for anyone else who may be interested in the solution.
The following code will display the ProgressRing and a TextBlock. The Text of the TextBlock will be updated with the name of the website that is being loaded. When all the websites have been loaded, the progress indication panel is hidden and the home panel is shown.
The XAML:
<Window
x:Class="CH11_ResponsiveWinUI3.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"
x:DefaultBindMode="TwoWay"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Visible">
<StackPanel x:Name="ProgressPanel">
<ProgressRing IsActive="True" />
<TextBlock x:Name="ProgressUpdater" Text="Loading..." TextAlignment="Left" TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis" />
</StackPanel>
<StackPanel x:Name="HomePanel" Visibility="Collapsed">
<TextBlock Text="Home Window" />
</StackPanel>
</StackPanel>
</Window>
The Code Behind (Edited as per the comments from #Clemens):
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace CH11_ResponsiveWinUI3
{
public sealed partial class MainWindow : Window
{
private DispatcherTimer _timer;
public MainWindow()
{
InitializeComponent();
_timer = new();
_timer.Interval = TimeSpan.FromSeconds(3);
_timer.Tick += Timer_Tick;
_timer.Start();
}
private async void Timer_Tick(object sender, object e)
{
_timer.Stop();
_timer.Tick -= Timer_Tick;
await GetWebsitesAsync();
}
private List<string> WebsiteLinks()
{
List<string> websiteLinks = new();
ProgressUpdater.Text = "Loading...";
websiteLinks.Add("https://learn.microsoft.com");
websiteLinks.Add("https://www.youtube.com");
websiteLinks.Add("https://www.abovetopsecret.com/index.php");
websiteLinks.Add("https://dotnet.microsoft.com/apps/aspnet");
websiteLinks.Add("https://www.packtpub.com/free-learning");
websiteLinks.Add("https://smile.amazon.com/");
return websiteLinks;
}
private async Task GetWebsitesAsync()
{
Dictionary<string, string> websites = new();
List<Task< Dictionary<string, string> >> tasks = new();
foreach(string website in WebsiteLinks())
{
string contents = await new HttpClient().GetStringAsync(new Uri(website));
websites.Add(website, contents);
ProgressUpdater.Text = $"\nURL: {website}, downloaded...";
}
ProgressUpdater.Text = "\nLoading completed.";
await Task.Delay(1000);
ProgressPanel.Visibility = Visibility.Collapsed;
HomePanel.Visibility = Visibility.Visible;
}
}
}
I hope you find this useful.

Updating value of TextBlock in wpf dynamically [Sovled]

I have text block on my UI. I would like to display some text on the text block dynamically. I have implemented it as given in the below code. however i do not see the values updating dynamically. I do see only the last updated value on UI text block. I have included a delay to notice the change.
Please provide any solution or comment for more info.Thank you in advance.
Code:
namespace TxtBlock
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
SomeObjectClass obj = new SomeObjectClass();
public MainWindow()
{
InitializeComponent();
txtName.DataContext = obj;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
obj.Name = "Hello World";
Thread.Sleep(2000);
obj.Name = "Third";
}
}
class SomeObjectClass : INotifyPropertyChanged
{
private string _name = "hello";
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
XAML: <Window x:Class="TxtBlock.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>
<Button Content="Button" HorizontalAlignment="Left" Margin="237,170,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<TextBlock HorizontalAlignment="Left" Margin="237,256,0,0" TextWrapping="Wrap" x:Name="txtName" Text="{Binding Name}" VerticalAlignment="Top"/>
</Grid>
</Window>
You need to Run in Background thread to update your values in UI TextBlock
Code:
public partial class TextBlockExample : Window
{
ThreadExampleViewModel viewModel = new ThreadExampleViewModel();
public TextBlockExample()
{
InitializeComponent();
this.DataContext = viewModel;
}
private void btnClick_Click(object sender, RoutedEventArgs e)
{
/// Background thread Thread to run your logic
Thread thread = new Thread(YourLogicToUpdateTextBlock);
thread.IsBackground = true;
thread.Start();
}
private void YourLogicToUpdateTextBlock()
{
/// Example i am updating with i value.
for (int i = 0; i < 1000; i++)
{
viewModel.Name = i + " Conut";
Thread.Sleep(1000);
}
}
}
<Grid>
<StackPanel>
<TextBlock x:Name="txtName" Text="{Binding Name}" Height="30" Width="100" Margin="10"/>
<Button x:Name="btnClick" Content="Click" Height="30" Width="100" Margin="10" Click="btnClick_Click"/>
</StackPanel>
</Grid>
public class ThreadExampleViewModel : INotifyPropertyChanged
{
private string name = "Hello";
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}

WPF GUI not updating on load when waiting for event

In my application I have a view that's being opened in the following way:
ManagerView view = new ManagerView();
view.ShowDialog();
This is the View:
<Window x:Class="WpfUpdateGui.ManagerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfUpdateGui">
<Window.DataContext>
<local:ManagerViewModel />
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContentRendered">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox Text="{Binding Messages}" />
and my ViewModel:
public class ManagerViewModel : INotifyPropertyChanged
{
/*INPC Members...*/
private string _messages;
private static EventWaitHandle _timerWaiter;
/*Constructor*/
public ManagerViewModel()
{
_timerWaiter = new EventWaitHandle(false, EventResetMode.AutoReset);
LoadedCommand = new RelayCommand(StartProcess);
}
private void StartProcess()
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.ApplicationIdle,
new Action(() =>
{
AddMessage("Starting");
Worker worker = new Worker();
worker.DidSomethingEvent += Worker_DidSomethingEvent;
worker.DoSomeThing();
_timerWaiter.WaitOne();
AddMessage("Finished");
}));
}
private void AddMessage(string message)
{
Application.Current.Dispatcher.Invoke(() => Messages += $"\r\n{message}");
}
private void Worker_DidSomethingEvent()
{
_timerWaiter.Set();
}
public RelayCommand LoadedCommand { get; set; }
public string Messages
{
get { return _messages; }
set
{
if (value == Messages) return;
_messages = value;
OnPropertyChanged("Messages");
}
}
}
public class Worker
{
public event Action DidSomethingEvent;
public void DoSomeThing()
{
Thread.Sleep(2500);
DidSomethingEvent();
}
}
My problem is that the first message I want to display ("Starting") is displayed only after the EventWaitHandle was set, even tough it was added before the WaitOne() call.
Just replace ContentRendered with Loaded event and it should work. ( It did work with me).

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

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.

Categories

Resources