I have a small WPF application where I'm simulating movement which is detected by a sensor. I fake that movement occurs after 1 minute and it stops after 2 minutes. Below is my code:
public event Action OnMotionDetected;
public event Action OnMotionReset;
private DateTime startTime = DateTime.Now;
public MotionDetectionService()
{
startTime = DateTime.Now;
System.Threading.Thread mockThread = new System.Threading.Thread(new System.Threading.ThreadStart(StartMock));
mockThread.Start();
}
private void StartMock()
{
while (DateTime.Now < startTime.AddMinutes(1))
{
System.Threading.Thread.Sleep(1000);
Console.WriteLine("Remaining: " + (startTime.AddMinutes(1) - DateTime.Now).ToString());
}
FireMoveEvent();
while (DateTime.Now < startTime.AddMinutes(2))
{
System.Threading.Thread.Sleep(1000);
Console.WriteLine("Remaining: " + (startTime.AddMinutes(2) - DateTime.Now).ToString());
}
FireMoveEvent();
}
private void FireMoveEvent()
{
if(OnMotionDetected != null)
{
OnMotionDetected();
}
}
private void FireResetEvent()
{
if (OnMotionReset != null)
{
OnMotionReset();
}
}
When the thread fires the event my UI updates, but it says that it cannot update because the UI elements was generated on another thread.
Any ideas on how to solve?
You can use Dispatcher.Invoke() to marshall onto the UI thread.
Please see: Making sure OnPropertyChanged() is called on UI thread in MVVM WPF app
Related
I am building an app with WPF and Caliburn.Micro. I want to update a ProgressBar from an Task/Thread and I am wondering what I need to correctly update the UI:
public class DemoViewModel : PropertyChangedBase
{
private int m_Progress;
public int Progress
{
get { return m_Progress; }
set
{
if (value == m_Progress) return;
m_Progress = value;
NotifyOfPropertyChange();
NotifyOfPropertyChange(nameof(CanStart));
}
}
public bool CanStart => Progress == 0 || Progress == 100;
public void Start()
{
Task.Factory.StartNew(example);
}
private void example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
Thread.Sleep(20);
}
}
}
From other programming languages I know that I need to synchronize with the UI thread to update the UI but my code just works. Is there something I missed and which could cause sporadic errors or is there some magic behind the scenes which care of the synchronization?
It will depend on how you've implemented INotifyPropertyChanged. The implementation should delgate all UI updates to the appropriate dispatcher.
Sample Implementation:
public void RaisePropertyChanged([CallerMemberName]string name) {
Application.Current.Dispatcher.Invoke(() => {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}, System.Windows.Threading.DispatcherPriority.Background);
}
Also to clean up the task start a bit:
Edit:
Removed unnecessary bool return value, and set ConfigureAwait to stay off UI thread when task completes.
public async void Start()
{
await Task.Run(() => example()).ConfigureAwait(false);
}
private async Task example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
await Task.Delay(20);
}
}
I have a WPF window with a button that spawns a BackgroundWorker thread to create and send an email. While this BackgroundWorker is running, I want to display a user control that displays some message followed by an animated "...". That animation is run by a timer inside the user control.
Even though my mail sending code is on a BackgroundWorker, the timer in the user control never gets called (well, it does but only when the Backgroundworker is finished, which kinda defeats the purpose...).
Relevant code in the WPF window:
private void button_Send_Click(object sender, RoutedEventArgs e)
{
busyLabel.Show(); // this should start the animation timer inside the user control
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
string body = textBox_Details.Text;
body += "User-added addtional information:" + textBox_AdditionalInfo.Text;
var smtp = new SmtpClient
{
...
};
using (var message = new MailMessage(fromAddress, toAddress)
{
Subject = subject,
Body = body
})
{
smtp.Send(message);
}
}));
}
Relevant code in the user control ("BusyLabel"):
public void Show()
{
tb_Message.Text = Message;
mTimer = new System.Timers.Timer();
mTimer.Interval = Interval;
mTimer.Elapsed += new ElapsedEventHandler(mTimer_Elapsed);
mTimer.Start();
}
void mTimer_Elapsed(object sender, ElapsedEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
int numPeriods = tb_Message.Text.Count(f => f == '.');
if (numPeriods >= NumPeriods)
{
tb_Message.Text = Message;
}
else
{
tb_Message.Text += '.';
}
}));
}
public void Hide()
{
mTimer.Stop();
}
Any ideas why it's locking up?
Using Dispatcher.Invoke in your worker_DoWork method is putting execution back on the UI thread, so you are not really doing the work asynchronously.
You should be able to just remove that, based on the code you are showing.
If there are result values that you need to show after the work is complete, put it in the DoWorkEventArgs and you will be able to access it (on the UI thread) in the worker_RunWorkerCompleted handler's event args.
A primary reason for using BackgroundWorker is that the marshalling is handled under the covers, so you shouldn't have to use Dispatcher.Invoke.
In my application's Business Logic layer I have the following classes:
public class EocMonitor : DeviceMonitor {
public BackgroundWorker BackendWorker { get; set; }
public BackgroundWorker EocWorker { get; set; }
public EocMonitor() {
BackendWorker = new BackgroundWorker {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
BackendWorker.DoWork += BackendWorker_DoWork;
EocWorker = new BackgroundWorker {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
EocWorker.DoWork += EocWorker_DoWork;
}
private void BackendWorker_DoWork( object sender, DoWorkEventArgs e ) {
// Does some lengthy operation
}
void EocWorker_DoWork( object sender, DoWorkEventArgs e ) {
// Does some lengthy operation
}
public void GetDiagnostics() {
BackendWorker.RunWorkerAsync( new DiagnosticsInfo() );
EocWorker.RunWorkerAsync( new DiagnosticsInfo() );
}
}
public class DiagnosticsInfo {
public int DataTypeCount { get; set; }
public int DataTypesProcessed { get; set; }
}
The BackgroundWorkers are used to query information over the wire from 2 other processes running in my application. The responses can take a while to come back. Plus the data can take a while to come back.
I have a WPF UserControl in my application's main window called Dashboard. The Dashboard has a DataGrid on it that displays the results of the lengthy operations. Because they are lengthy, it also has a Button on it called Refresh that starts the process off. And, because it can take a long time to run, there's a UserControl I wrote called a ProgressControl on the form. This consists of a Cancel Button, a ProgressBar, and a TextBlock where messages can be displayed. When the user clicks on the Cancel Button, the refresh stops.
Here's some code from Dashboard:
public partial class Dashboard : UserControl {
public Dashboard() {
InitializeComponent();
}
private Dashboard_Loaded( object sender, RoutedEventArgs e ) {
if ( !setupProgress && EocMonitor != null ) {
EocMonitor.BackendWorker.ProgressChanged += BackendWorker_ProgressChanged;
EocMonitor.BAckendWorker.RunWorkerCompleted += BackendWorker_RunWorkerCompleted;
EocMonitor.EocWorker.ProgressChkanged += EocWorker_ProgresChanged;
EocMonitor.EocWorker.RunWorkerCompleted += EocWorker_RunWorkerCompleted;
}
}
private void BackendWorker_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
DiagnosticsInfo info = e.UserState as DiagnosticsInfo;
// Other processing to notify the user of the progress
}
private void BackendWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
// Processing to do when the back-ground worker is finished
}
private void DiagnosticsProgressCtrl_Click( object sender, RoutedEventArgs e ) {
EocMonitor.BackendWorker.CancelAsync();
EocMonitor. EocWorker.CancelAsync();
DiagnosticsProgressCtrl.Visibility = Visibility.Collapsed;
e.Handled = true;
}
void EocWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
// Processing to do when the back-ground worker is finished
}
private void RefreshButton_Click( object sender, RoutedEventArgs e ) {
DiagnosticsProgressCtrl.Maximum = DiagnosticsProgressCtrl.Minimum = DiagnosticsProgressCtrl.Value = 0.0;
DiagnosticsProgressCtrl.Visibility = Visibility.Visible;
backendDataTypeCount = eocDataTypeCount = 0;
backendWorkerCompleted = eocWorkerCompleted = false;
EocMonitor.GetDiagnostics();
e.Handled = true;
}
}
The problem is that I have placed breakpoints in the DoWork methods and watched them run to completion, yet the RunWorkerCompleted methods are not being called. No errors are occurring or being thrown. This thing is the EocMonitor class and the Dashboard class are in two different DLLs. Does that make a difference? As far as I know it shouldn't, but I don't understand why the completed event handlers aren't getting called. Should I instantiate the BackgroundWorkers in the front-end application?
Tony
The event is raised, but you don't see it because you didn't subscribe to the RunWorkerCompleted event...
BackendWorker.RunWorkerCompleted += BackendWorker_RunWorkerCompleted;
EocWorker.RunWorkerCompleted += EocWorker_RunWorkerCompleted;
Well, after I posted the above, I went back and changed things a bit. I now instantiate the BackgroundWorker objects in the Dashboard control and pass them to the EocMonitor's GetDiagnostics method. The properties in EocMonitor that hold these objects have private setters, so the only way to use them is to create them & pass them to that method. The code in the Dashboard_Loaded is now moved in the RefreshButton_Click method and runs after the objects are instantiated, before they're passed to GetDiagnostics.
This all works now! I see the Progress_Changed methods and the RunWorkerCompleted methods run.
It just hit me why it's probably not working. The EocMonitor object is created on a non UI thread during my program's initalization phase. Since it's calling methods in a UI object, the methods probably can't be called. An Invalid operation exception of some sort is probably being thrown, but there's no place to catch it.
So let that be a lesson: The BackgroundWorker has to be instantiated in code on the UI thread.
I have the synfusion tile view as below.
Maximized Item template for these Items is TreeView. the treeview items Source is bind to the Observable Collection. When I Maximize one of these Items, it will load the data from ViewModel as below. It's in the MaximizedItemChanged Event.
private void tileViewControl_Exchanges_MaximizedItemChanged(object sender, TileViewEventArgs args)
{
if (args.Source != null)
{
TileViewControl tileViewControl = (TileViewControl)sender;
TileViewItem tvi = (TileViewItem)args.Source;
PanelViewModel panelViewModel = (PanelViewModel)tileViewControl.ItemContainerGenerator.ItemFromContainer(tvi);
String currentSelectedPanelID = GetSelectedPanelID(tileViewControl);
// below function will load all the treeview items.
SetSelectedExchangeID(tileViewControl, exchangePanelViewModel.ExchangeID);
}
}
But treeview has over thousands of items. So after clicking on Maximize, It will take a while and the program hang. Is there a way to maximize the Item smoothly first and load the Treeview Item at the background? What I'd like to do is I will show the loading animation while the treeview is loading but now, when it maximized (after hanging for 8 or 9 secs) , the treeview is already loaded.
Edit: I added the code fore SetSelectedExchangeID.
public static readonly DependencyProperty SelectedExchangeIDProperty =
DependencyProperty.RegisterAttached("SelectedExchangeID",
typeof(String),
typeof(UC_Contract_List),
new UIPropertyMetadata(new PropertyChangedCallback(SelectedExchangeIDPropertyChanged)));
static void SelectedExchangeIDPropertyChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs eventArgs)
{
TileViewControl tileViewControl = (TileViewControl)depObj;
ItemContainerGenerator itemContainerGenerator = tileViewControl.ItemContainerGenerator;
String newPanelID = (String)eventArgs.NewValue;
if (newPanelID != null)
{
if (tileViewControl.Visibility == Visibility.Visible)
{
foreach (PanelViewModel exchangePanel in tileViewControl.Items)
{
if (exchangePanel.ExchangeID.Equals(newExchangeID))
{
TileViewItem tvi = (TileViewItem)itemContainerGenerator.ContainerFromItem(exchangePanel);
try
{
if (tileViewControl.tileViewItems != null)
{
if (tvi.TileViewItemState != TileViewItemState.Maximized)
{
tvi.TileViewItemState = TileViewItemState.Maximized;
}
}
}
catch (Exception e) { }
break;
}
}
}
}
else
{
foreach (PanelViewModel exchangePanel in tileViewControl.Items)
{
TileViewItem tvi = (TileViewItem)itemContainerGenerator.ContainerFromItem(exchangePanel);
tvi.TileViewItemState = TileViewItemState.Normal;
}
}
}
public static void SetSelectedExchangeID(DependencyObject depObj, String exchangeID)
{
depObj.SetValue(SelectedExchangeIDProperty, exchangeID);
}
public static String GetSelectedExchangeID(DependencyObject depObj)
{
return (String)depObj.GetValue(SelectedExchangeIDProperty);
}
And in ViewModel:
String _selectedExchangeID;
public String SelectedExchangeID
{
get { return this._selectedExchangeID; }
set
{
if (value == null)
{
this.ClearPanels();
this._selectedExchangeID = value;
}
else
{
this._selectedExchangeID = value;
PanelViewModel curPanelViewModel = this.GetPanelViewModel(this._selectedExchangeID);
if (curPanelViewModel != null)
{
curPanelViewModel.Activate(); // this will add to the observable collection for Treeview ItemsSource
}
}
this.OnPropertyChanged("SelectedExchangeID");
}
}
You can do that by doing the processing/heavy loading task asynchronously on a background thread and syncing with foreground thread using UI Dispatcher object only when everything is available and processed.
For details on BackgroundWorker refer to MSDN.
Please note BackgroundWorker is not the only way to do async task. you may opt for Tasks (introduced in .net 4.0) or BeginInvoke/EndInvoke.
And When you are done with Heavy task you may sync with foreground thread in the following way.
First initialize dispatcher on UI thread (lets say Views Constructor):
Dispatcher _UIDispatcher;
public MyView
{
...
_UIDispatcher = Dispatcher.CurrentDispatcher;
}
Then sync in after loading is complete:
public void SyncPostLoading(IEnumerable<Something> myData)
{
_UIDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, System.Threading.ThreadStart)delegate()
{
foreach(Something something in myData)
myObervableCollection.Add(something);
});
}
You have a couple of different options for how to do you work on a background thread. You can use the backgroundworker (slightly outdated) or the .NET 4.0 Tasks (part of the Task Parallel Library). You need to decide if you want to load all of the data into a new collection and invoke an update onto the GUI thread all at once or if you want to load the items in batches and invoke those batches onto the GUI in several rounds
I have an MVVM kiosk application that I need to restart when it has been inactive for a set amount of time. I'm using Prism and Unity to facilitate the MVVM pattern. I've got the restarting down and I even know how to handle the timer. What I want to know is how to know when activity, that is any mouse event, has taken occurred. The only way I know how to do that is by subscribing to the preview mouse events of the main window. That breaks MVVM thought, doesn't it?
I've thought about exposing my window as an interface that exposes those events to my application, but that would require that the window implement that interface which also seems to break MVVM.
Another option is to use the Windows API method GetLastInputInfo.
Some cavets
I'm assuming Windows because it's WPF
Check if your kiosk supports GetLastInputInfo
I don't know anything about MVVM. This method uses a technique that is UI agnostic, so I would think it would work for you.
Usage is simple. Call UserIdleMonitor.RegisterForNotification. You pass in a notification method and a TimeSpan. If user activity occurs and then ceases for the period specified, the notification method is called. You must re-register to get another notification, and can Unregister at any time. If there is no activity for 49.7 days (plus the idlePeriod), the notification method will be called.
public static class UserIdleMonitor
{
static UserIdleMonitor()
{
registrations = new List<Registration>();
timer = new DispatcherTimer(TimeSpan.FromSeconds(1.0), DispatcherPriority.Normal, TimerCallback, Dispatcher.CurrentDispatcher);
}
public static TimeSpan IdleCheckInterval
{
get { return timer.Interval; }
set
{
if (Dispatcher.CurrentDispatcher != timer.Dispatcher)
throw new InvalidOperationException("UserIdleMonitor can only be used from one thread.");
timer.Interval = value;
}
}
public sealed class Registration
{
public Action NotifyMethod { get; private set; }
public TimeSpan IdlePeriod { get; private set; }
internal uint RegisteredTime { get; private set; }
internal Registration(Action notifyMethod, TimeSpan idlePeriod)
{
NotifyMethod = notifyMethod;
IdlePeriod = idlePeriod;
RegisteredTime = (uint)Environment.TickCount;
}
}
public static Registration RegisterForNotification(Action notifyMethod, TimeSpan idlePeriod)
{
if (notifyMethod == null)
throw new ArgumentNullException("notifyMethod");
if (Dispatcher.CurrentDispatcher != timer.Dispatcher)
throw new InvalidOperationException("UserIdleMonitor can only be used from one thread.");
Registration registration = new Registration(notifyMethod, idlePeriod);
registrations.Add(registration);
if (registrations.Count == 1)
timer.Start();
return registration;
}
public static void Unregister(Registration registration)
{
if (registration == null)
throw new ArgumentNullException("registration");
if (Dispatcher.CurrentDispatcher != timer.Dispatcher)
throw new InvalidOperationException("UserIdleMonitor can only be used from one thread.");
int index = registrations.IndexOf(registration);
if (index >= 0)
{
registrations.RemoveAt(index);
if (registrations.Count == 0)
timer.Stop();
}
}
private static void TimerCallback(object sender, EventArgs e)
{
LASTINPUTINFO lii = new LASTINPUTINFO();
lii.cbSize = Marshal.SizeOf(typeof(LASTINPUTINFO));
if (GetLastInputInfo(out lii))
{
TimeSpan idleFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - lii.dwTime));
//Trace.WriteLine(String.Format("Idle for {0}", idleFor));
for (int n = 0; n < registrations.Count; )
{
Registration registration = registrations[n];
TimeSpan registeredFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - registration.RegisteredTime));
if (registeredFor >= idleFor && idleFor >= registration.IdlePeriod)
{
registrations.RemoveAt(n);
registration.NotifyMethod();
}
else n++;
}
if (registrations.Count == 0)
timer.Stop();
}
}
private static List<Registration> registrations;
private static DispatcherTimer timer;
private struct LASTINPUTINFO
{
public int cbSize;
public uint dwTime;
}
[DllImport("User32.dll")]
private extern static bool GetLastInputInfo(out LASTINPUTINFO plii);
}
Updated
Fixed issue where if you tried to re-register from the notification method you could deadlock.
Fixed unsigned math and added unchecked.
Slight optimization in timer handler to allocate notifications only as needed.
Commented out the debugging output.
Altered to use DispatchTimer.
Added ability to Unregister.
Added thread checks in public methods as this is no longer thread-safe.
You could maybe use MVVM Light's EventToCommand behaviour to link the MouseMove/MouseLeftButtonDown event to a command. This is normally done in blend because it's really easy.
Here's some example xaml if you don't have blend:
<Grid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding theCommand} />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
Where i: is a xml namespace for Blend.Interactivity.
This is not an official answer, but here is my version of UserIdleMonitor for anyone who is interested:
public class UserIdleMonitor
{
private DispatcherTimer _timer;
private TimeSpan _timeout;
private DateTime _startTime;
public event EventHandler Timeout;
public UserIdleMonitor(TimeSpan a_timeout)
{
_timeout = a_timeout;
_timer = new DispatcherTimer(DispatcherPriority.Normal, Dispatcher.CurrentDispatcher);
_timer.Interval = TimeSpan.FromMilliseconds(100);
_timer.Tick += new EventHandler(timer_Tick);
}
public void Start()
{
_startTime = new DateTime();
_timer.Start();
}
public void Stop()
{
_timer.Stop();
}
private void timer_Tick(object sender, EventArgs e)
{
LASTINPUTINFO lii = new LASTINPUTINFO();
lii.cbSize = Marshal.SizeOf(typeof(LASTINPUTINFO));
if (GetLastInputInfo(out lii))
{
TimeSpan idleFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - lii.dwTime));
TimeSpan aliveFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - _startTime.Millisecond));
Debug.WriteLine(String.Format("aliveFor = {0}, idleFor = {1}, _timeout = {2}", aliveFor, idleFor, _timeout));
if (aliveFor >= idleFor && idleFor >= _timeout)
{
_timer.Stop();
if (Timeout != null)
Timeout.Invoke(this, EventArgs.Empty);
}
}
}
#region Win32 Stuff
private struct LASTINPUTINFO
{
public int cbSize;
public uint dwTime;
}
[DllImport("User32.dll")]
private extern static bool GetLastInputInfo(out LASTINPUTINFO plii);
#endregion
}
The only way I know how to do that is by subscribing to the preview mouse events of the main window. That breaks MVVM thought, doesn't it?
That really depends on how you do it.
You could pretty easily write a Behavior or an Attached Property that you hook into this event and use it to trigger an ICommand in your ViewModel. This way, you're basically pushing a "Something happened" event down to the VM, where you can handle this completely in your business logic.