BackgroundWorker not raising RunWorkerCompleted event - wpf

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.

Related

Determine whether Selector.SelectionChanged event was initiated by a user

Is it possible to determine whether a Selector.SelectionChanged event was initiated by the user or programmatically?
I.e. I need something like a boolean "IsUserInitiated" property that is true only if the SelectionChanged event was raised because the user changed the selection using mouse or keyboard.
Simple work around:
You could create a method that temporarily disables the SelectionChanged event and call it when you need to change the selection programmatically.
private void SelectGridRow( int SelectedIndex )
{
myDataGrid.SelectionChanged -= myDataGrid_SelectionChanged;
myDataGrid.SelectedIndex = SelectedIndex;
// other work ...
myDataGrid.SelectionChanged += myDataGrid_SelectionChanged;
}
This should work in most scenarios:
private void cboStatus_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.cboStatus.IsDropDownOpen)
{
//OPTIONAL:
//Causes the combobox selection changed to not be fired again if anything
//in the function below changes the selection (as in my weird case)
this.cboStatus.IsDropDownOpen = false;
//now put the code you want to fire when a user selects an option here
}
}
This is a problem I have had to work around since WinForms. I was hoping that in WPF they would add a boolean to SelectionChangedEventArgs called something like IsUserInitiated as mentioned in the question. I have most commonly needed this when I want to ignore anything happening while the data is loading and binding to the screen. For example, say I am defaulting a field based on the new value in SelectionChanged BUT I want the user to be able to overwrite this default value, and I only want the user to overwrite it, NOT the application when the screen reloads. I still feel like what I have been doing is hacky, but I will post it because I don't see it mentioned. No fancy tricks, just simple and effective.
1) Create a class level boolean called _loading
private bool _loading;
2) Update the boolean in the method doing the loading
private async Task Load()
{
_loading = true;
//load some stuff
_loading = false;
}
3) Use the boolean whenever you need to
private void SetDefaultValue(object sender, SelectionChangedEventArgs e)
{
if (!_loading) {
//set a default value
}
}
Taken from http://social.msdn.microsoft.com where the user post the same question
I don't think we can distinguish whether a SelectionChanged event was initiated by the user input or programmatically. SelectionChanged event doesn't care that.
Generally, you can always now whether it is initiated programmatically because it's your code that initiates it.
If you use DataBinding to bind the SelectedItem, you can set the NotifyOnSourceUpdated and NotifyOnTargetUpdated properties to True. And you can handle the Binding.SourceUpdated and Binding.TargetUpdated events. In most cases, the change initiated by the user inputs goes from Target to Source. If the change is initiated programmatically, it goes from Source to Target.
I don't know if it can help...
You could use an custom routed event and hook up the appropriate handlers in an behavior like this:
public class UserSelectionChangedEventArgs : RoutedEventArgs
{
public UserSelectionChangedEventArgs( RoutedEvent id, SelectionChangedEventArgs args , bool changedByUser) :base(id)
{
SelectionChangedByUser = changedByUser;
RemovedItems = args.RemovedItems;
AddedItems = args.AddedItems;
}
public bool SelectionChangedByUser { get; set; }
public IList RemovedItems { get; set; }
public IList AddedItems { get; set; }
}
public delegate void UserSelectionChangedEventHandler( object sender, UserSelectionChangedEventArgs e );
public class UserSelectionChangedBehavior : Behavior<Selector>
{
private bool m_expectingSelectionChanged;
public static readonly RoutedEvent UserSelectionChangedEvent = EventManager.RegisterRoutedEvent( "UserSelectionChanged", RoutingStrategy.Bubble, typeof( UserSelectionChangedEventHandler ), typeof( Selector ) );
public static void AddUserSelectionChangedHandler( DependencyObject d, UserSelectionChangedEventHandler handler )
{
( (Selector) d ).AddHandler( UserSelectionChangedEvent, handler );
}
public static void RemoveUserSelectionChangedHandler( DependencyObject d, UserSelectionChangedEventHandler handler )
{
( (Selector) d ).RemoveHandler( UserSelectionChangedEvent, handler );
}
private void RaiseUserSelectionChangedEvent( UserSelectionChangedEventArgs args )
{
AssociatedObject.RaiseEvent( args );
}
protected override void OnAttached()
{
AssociatedObject.PreviewKeyDown += OnKeyDown;
AssociatedObject.PreviewKeyUp += OnKeyUp;
AssociatedObject.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
AssociatedObject.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
AssociatedObject.SelectionChanged += OnSelectionChanged;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= OnKeyDown;
AssociatedObject.PreviewKeyUp -= OnKeyUp;
AssociatedObject.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDown;
AssociatedObject.PreviewMouseLeftButtonUp -= OnMouseLeftButtonUp;
AssociatedObject.SelectionChanged -= OnSelectionChanged;
base.OnDetaching();
}
private void OnMouseLeftButtonUp( object sender, MouseButtonEventArgs e )
{
m_expectingSelectionChanged = false;
}
private void OnKeyDown( object sender, KeyEventArgs e )
{
m_expectingSelectionChanged = true;
}
private void OnKeyUp( object sender, KeyEventArgs e )
{
m_expectingSelectionChanged = false;
}
private void OnMouseLeftButtonDown( object sender, MouseButtonEventArgs e )
{
m_expectingSelectionChanged = true;
}
private void OnSelectionChanged( object sender, SelectionChangedEventArgs e )
{
RaiseUserSelectionChangedEvent( new UserSelectionChangedEventArgs( UserSelectionChangedEvent, e, m_expectingSelectionChanged ) );
}
}
In XAML you could just subscribe to the UserSelectionChangedEvent like this:
<ListBox ItemsSource="{Binding Items}" b:UserSelectionChangedBehavior.UserSelectionChanged="OnUserSelectionChanged">
<i:Interaction.Behaviors>
<b:UserSelectionChangedBehavior/>
</i:Interaction.Behaviors>
Handler:
private void OnUserSelectionChanged( object sender, UserSelectionChangedEventArgs e )
{
if(e.SelectionChangedByUser)
{
Console.WriteLine( "Selection changed by user" );
}
else
{
Console.WriteLine( "Selection changed by code" );
}
}
This is just an idea. Probably you won't even need the behavior and just define the attached routed event. But then I have no idea where to store the m_expectingSelectionChanged flag. I also don't know if this works in all cases. But maybe it gives you a starting point.
Usually a Selector has it's selection set/changed when the control is loaded into view. When this happens the IsLoaded property is still false. When a user makes a selection manually the control obviously has to be visible and hence IsLoaded will be true. Try using this property to determine if a change is user initiated or due to the control being loaded.
Why do you want to know?
I have coded many dialogs where I had similar situations - I didn't really want to know that the user used the mouse or keyboard, but I did want a specific behaviour, and I did want effects from triggering some binding to behave the right way.
For most cases I have found that using the MVVM pattern - or at least separating logic from ui - you often avoid those problems.
So for your problem I would try to eliminate the selectionchanged handler and only use bindings - so your state of the gui is based on the model behind and not the wireing of events.
mvvm:
http://en.wikipedia.org/wiki/Model_View_ViewModel
You can check for AddedItems and RemovedItems. If it was initiated by user both properties has an item. If an item was just added via code the RemovedItems list should be empty. So
if (e.AddedItems.Count>0 && e.RemovedItems.Count > 0)
//Initiated by user

How do I determine inactivity in a MVVM application?

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.

Threading problem in WPF

I'm getting this Exception
System.InvalidOperationException was
unhandled by user code Message="The
calling thread cannot access this
object because a different thread owns
it."
whenever I run the following code
public partial class MainScreen : Window
{
Timer trm;
public MainScreen()
{
InitializeComponent();
trm = new Timer(1000);
trm.AutoReset = true;
trm.Start();
trm.Elapsed += new ElapsedEventHandler(trm_Elapsed);
}
void trm_Elapsed(object sender, ElapsedEventArgs e)
{
lblTime.Content = System.DateTime.Now;
}
}
guys any solution... I badly wann come out of it :(
Use DispatcherTimer instead:
public partial class MainScreen : Window{
DispatcherTimer tmr;
public MainScreen() {
InitializeComponent();
tmr = new DispatcherTimer();
tmr.Tick += new EventHandler(tmr_Tick);
tmr.Start();
}
void tmr_Tick(object sender, EventArgs e) {
lblTime.Content = System.DateTime.Now;
}
}
Any time you modify Windows controls you must do so on the UI thread (the one that created the controls).
See this question for lots of details.
To be short, you should use Dispatcher.Invoke method to update UI elements.

WPF: Slider with an event that triggers after a user drags

I am currently making an MP3 player in WPF, and I want to make a slider that will allow the user to seek to a particular position in an MP3 by sliding the slider to the left or right.
I have tried using the ValueChanged event but that triggers every time it's value is changed, so if you drag it across, the event will fire multiple times, I want the event to only fire when the user has finished dragging the slider and Then get the new value.
How can I achieve this?
[Update]
I have found this post on MSDN which basically discusses the same thing, and they came up with two "solutions"; either subclassing the Slider or invoking a DispatcherTimer in the ValueChanged event that invokes the action after a timespan.
Can you come up with anything better then the two mentioned above?
Besides using the Thumb.DragCompleted event you can also use both ValueChanged and Thumb.DragStarted, this way you don’t lose functionality when the user modifies the value by pressing the arrow keys or by clicking on the slider bar.
Xaml:
<Slider ValueChanged="Slider_ValueChanged"
Thumb.DragStarted="Slider_DragStarted"
Thumb.DragCompleted="Slider_DragCompleted"/>
Code behind:
private bool dragStarted = false;
private void Slider_DragCompleted(object sender, DragCompletedEventArgs e)
{
DoWork(((Slider)sender).Value);
this.dragStarted = false;
}
private void Slider_DragStarted(object sender, DragStartedEventArgs e)
{
this.dragStarted = true;
}
private void Slider_ValueChanged(
object sender,
RoutedPropertyChangedEventArgs<double> e)
{
if (!dragStarted)
DoWork(e.NewValue);
}
You can use the thumb's 'DragCompleted' event for this. Unfortunately, this is only fired when dragging, so you'll need to handle other clicks and key presses separately. If you only want it to be draggable, you could disable these means of moving the slider by setting LargeChange to 0 and Focusable to false.
Example:
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
<Slider PreviewMouseUp="MySlider_DragCompleted" />
works for me.
The value you want is the value after a mousup event, either on clicks on the side or after a drag of the handle.
Since MouseUp doesn't tunnel down (it is handeled before it can), you have to use PreviewMouseUp.
Another MVVM-friendly solution (I was not happy with answers)
View:
<Slider Maximum="100" Value="{Binding SomeValue}"/>
ViewModel:
public class SomeViewModel : INotifyPropertyChanged
{
private readonly object _someValueLock = new object();
private int _someValue;
public int SomeValue
{
get { return _someValue; }
set
{
_someValue = value;
OnPropertyChanged();
lock (_someValueLock)
Monitor.PulseAll(_someValueLock);
Task.Run(() =>
{
lock (_someValueLock)
if (!Monitor.Wait(_someValueLock, 1000))
{
// do something here
}
});
}
}
}
It's delayed (by 1000 ms in given example) operation. New task is created for every change done by slider (either by mouse or keyboard). Before starting task it signals (by using Monitor.PulseAll, perhaps even Monitor.Pulse would be enough) to running already tasks (if any) to stop. Do something part will only occurs when Monitor.Wait don't get signal within timeout.
Why this solution? I don't like spawning behavior or having unnecessary event handling in the View. All code is in one place, no extra events needed, ViewModel has choice to either react on each value change or at the end of user operation (which adds tons of flexibility, especially when using binding).
My implementation is based on #Alan's and #SandRock's answer:
public class SliderValueChangeByDragBehavior : Behavior<Slider>
{
private bool hasDragStarted;
/// <summary>
/// On behavior attached.
/// </summary>
protected override void OnAttached()
{
AssociatedObject.AddHandler(Thumb.DragStartedEvent, (DragStartedEventHandler)Slider_DragStarted);
AssociatedObject.AddHandler(Thumb.DragCompletedEvent, (DragCompletedEventHandler)Slider_DragCompleted);
AssociatedObject.ValueChanged += Slider_ValueChanged;
base.OnAttached();
}
/// <summary>
/// On behavior detaching.
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(Thumb.DragStartedEvent, (DragStartedEventHandler)Slider_DragStarted);
AssociatedObject.RemoveHandler(Thumb.DragCompletedEvent, (DragCompletedEventHandler)Slider_DragCompleted);
AssociatedObject.ValueChanged -= Slider_ValueChanged;
}
private void updateValueBindingSource()
=> BindingOperations.GetBindingExpression(AssociatedObject, RangeBase.ValueProperty)?.UpdateSource();
private void Slider_DragStarted(object sender, DragStartedEventArgs e)
=> hasDragStarted = true;
private void Slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
hasDragStarted = false;
updateValueBindingSource();
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!hasDragStarted)
updateValueBindingSource();
}
}
You can apply it in that way:
...
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:myWhateverNamespace="clr-namespace:My.Whatever.Namespace;assembly=My.Whatever.Assembly"
...
<Slider
x:Name="srUserInterfaceScale"
VerticalAlignment="Center"
DockPanel.Dock="Bottom"
IsMoveToPointEnabled="True"
Maximum="{x:Static localLibraries:Library.MAX_USER_INTERFACE_SCALE}"
Minimum="{x:Static localLibraries:Library.MIN_USER_INTERFACE_SCALE}"
Value="{Binding Source={x:Static localProperties:Settings.Default}, Path=UserInterfaceScale, UpdateSourceTrigger=Explicit}">
<i:Interaction.Behaviors>
<myWhateverNamespace:SliderValueChangeByDragBehavior />
</i:Interaction.Behaviors>
</Slider>
I've set the UpdateSourceTrigger to explicit, as the behaviour does it. And you are in need of the nuget package Microsoft.Xaml.Behaviors(.Wpf/.Uwp.Managed).
Here is a behavior that handles this problem plus the same thing with the keyboard. https://gist.github.com/4326429
It exposes a Command and Value properties. The value is passed as the parameter of the command. You can databind to the value property (and use it in the viewmodel). You may add an event handler for a code-behind approach.
<Slider>
<i:Interaction.Behaviors>
<b:SliderValueChangedBehavior Command="{Binding ValueChangedCommand}"
Value="{Binding MyValue}" />
</i:Interaction.Behaviors>
</Slider>
My solution is basically Santo's solution with a few more flags. For me, the slider is being updated from either reading the stream or the user manipulation (either from a mouse drag or using the arrow keys etc)
First, I had wrote the code to update the slider value from reading the stream:
delegate void UpdateSliderPositionDelegate();
void UpdateSliderPosition()
{
if (Thread.CurrentThread != Dispatcher.Thread)
{
UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
Dispatcher.Invoke(function, new object[] { });
}
else
{
double percentage = 0; //calculate percentage
percentage *= 100;
slider.Value = percentage; //this triggers the slider.ValueChanged event
}
}
I then added my code that captured when the user was manipulating the slider with a mouse drag:
<Slider Name="slider"
Maximum="100" TickFrequency="10"
ValueChanged="slider_ValueChanged"
Thumb.DragStarted="slider_DragStarted"
Thumb.DragCompleted="slider_DragCompleted">
</Slider>
And added the code behind:
/// <summary>
/// True when the user is dragging the slider with the mouse
/// </summary>
bool sliderThumbDragging = false;
private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
sliderThumbDragging = true;
}
private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
sliderThumbDragging = false;
}
When the user updates the slider's value with a mouse drag, the value will still change due to the stream being read and calling UpdateSliderPosition(). To prevent conflicts, UpdateSliderPosition() had to be changed:
delegate void UpdateSliderPositionDelegate();
void UpdateSliderPosition()
{
if (Thread.CurrentThread != Dispatcher.Thread)
{
UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
Dispatcher.Invoke(function, new object[] { });
}
else
{
if (sliderThumbDragging == false) //ensure user isn't updating the slider
{
double percentage = 0; //calculate percentage
percentage *= 100;
slider.Value = percentage; //this triggers the slider.ValueChanged event
}
}
}
While this will prevent conflicts, we are still unable to determine whether the value is being updated by the user or by a call to UpdateSliderPosition(). This is fixed by yet another flag, this time set from within UpdateSliderPosition().
/// <summary>
/// A value of true indicates that the slider value is being updated due to the stream being read (not by user manipulation).
/// </summary>
bool updatingSliderPosition = false;
delegate void UpdateSliderPositionDelegate();
void UpdateSliderPosition()
{
if (Thread.CurrentThread != Dispatcher.Thread)
{
UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
Dispatcher.Invoke(function, new object[] { });
}
else
{
if (sliderThumbDragging == false) //ensure user isn't updating the slider
{
updatingSliderPosition = true;
double percentage = 0; //calculate percentage
percentage *= 100;
slider.Value = percentage; //this triggers the slider.ValueChanged event
updatingSliderPosition = false;
}
}
}
Finally, we're able to detect whether the slider is being updated by the user or by the call to UpdateSliderPosition():
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (updatingSliderPosition == false)
{
//user is manipulating the slider value (either by keyboard or mouse)
}
else
{
//slider value is being updated by a call to UpdateSliderPosition()
}
}
Hope that helps someone!
If you want to get the manipulation ended information even if the user is not using the thumb to change the value (ie clicking somewhere in the track bar), you can attach an event handler to your slider for the pointer pressed and capture lost events. You can do the same thing for the keyboard events
var pointerPressedHandler = new PointerEventHandler(OnSliderPointerPressed);
slider.AddHandler(Control.PointerPressedEvent, pointerPressedHandler, true);
var pointerCaptureLostHandler = new PointerEventHandler(OnSliderCaptureLost);
slider.AddHandler(Control.PointerCaptureLostEvent, pointerCaptureLostHandler, true);
var keyDownEventHandler = new KeyEventHandler(OnSliderKeyDown);
slider.AddHandler(Control.KeyDownEvent, keyDownEventHandler, true);
var keyUpEventHandler = new KeyEventHandler(OnSliderKeyUp);
slider.AddHandler(Control.KeyUpEvent, keyUpEventHandler, true);
The "magic" here is the AddHandler with the true parameter at the end which allows us to get the slider "internal" events.
The event handlers :
private void OnKeyDown(object sender, KeyRoutedEventArgs args)
{
m_bIsPressed = true;
}
private void OnKeyUp(object sender, KeyRoutedEventArgs args)
{
Debug.WriteLine("VALUE AFTER KEY CHANGE {0}", slider.Value);
m_bIsPressed = false;
}
private void OnSliderCaptureLost(object sender, PointerRoutedEventArgs e)
{
Debug.WriteLine("VALUE AFTER CHANGE {0}", slider.Value);
m_bIsPressed = false;
}
private void OnSliderPointerPressed(object sender, PointerRoutedEventArgs e)
{
m_bIsPressed = true;
}
The m_bIsPressed member will be true when the user is currently manipulating the slider (click, drag or keyboard). It will be reset to false once done .
private void OnValueChanged(object sender, object e)
{
if(!m_bIsPressed) { // do something }
}
This subclassed version of the Slider wokrs as you want:
public class NonRealtimeSlider : Slider
{
static NonRealtimeSlider()
{
var defaultMetadata = ValueProperty.GetMetadata(typeof(TextBox));
ValueProperty.OverrideMetadata(typeof(NonRealtimeSlider), new FrameworkPropertyMetadata(
defaultMetadata.DefaultValue,
FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
defaultMetadata.PropertyChangedCallback,
defaultMetadata.CoerceValueCallback,
true,
UpdateSourceTrigger.Explicit));
}
protected override void OnThumbDragCompleted(DragCompletedEventArgs e)
{
base.OnThumbDragCompleted(e);
GetBindingExpression(ValueProperty)?.UpdateSource();
}
}
I liked Answer by #sinatr.
My Solution Based on Answer Above:
This solution cleans up the code a lot and encapsulates the mechanism.
public class SingleExecuteAction
{
private readonly object _someValueLock = new object();
private readonly int TimeOut;
public SingleExecuteAction(int timeOut = 1000)
{
TimeOut = timeOut;
}
public void Execute(Action action)
{
lock (_someValueLock)
Monitor.PulseAll(_someValueLock);
Task.Run(() =>
{
lock (_someValueLock)
if (!Monitor.Wait(_someValueLock, TimeOut))
{
action();
}
});
}
}
Use it in Your class as:
public class YourClass
{
SingleExecuteAction Action = new SingleExecuteAction(1000);
private int _someProperty;
public int SomeProperty
{
get => _someProperty;
set
{
_someProperty = value;
Action.Execute(() => DoSomething());
}
}
public void DoSomething()
{
// Only gets executed once after delay of 1000
}
}
My solution for the WinUI3 v1.2.2 follows here:
Xaml file:
<Slider Margin="10, 0" MinWidth="200" LargeChange="0.5"
TickPlacement="BottomRight" TickFrequency="10"
SnapsTo="StepValues" StepFrequency="5"
Maximum="719"
Value="{x:Bind Path=XamlViewModel.XamlSliderToDateInt, Mode=TwoWay}">
</Slider>
To-Date slider property:
private int _sliderToDateInt;
public int XamlSliderToDateInt
{
get { return _sliderToDateInt; }
set
{
SetProperty(ref _sliderToDateInt, value);
_myDebounceTimer.Debounce(() =>
{
this.XamlSelectedTimeChangedTo = TimeSpan.FromMinutes(value);
// time-expensive methods:
this.XamlLCModel = _myOxyPlotModel.UpdatePlotModel(_myLCPowerRecList, XamlSliderFromDateInt, XamlSliderToDateInt, _myOxyPlotPageOptions);
this.XamlTRModel = _myOxyPlotModel.UpdatePlotModel(_myTRPowerRecList, XamlSliderFromDateInt, XamlSliderToDateInt, _myOxyPlotPageOptions);
},
TimeSpan.FromSeconds(0.6));
}
}
Timer declaration:
private DispatcherQueueTimer _myDebounceTimer;
Timer initialization in constructor:
_myDebounceTimer = _dispatcherQueue.CreateTimer();
The method _myOxyPlotModel.UpdatePlotModel() will be called not faster than every 0.6sec, even when the XamlSliderToDateInt property is updated much faster by dragging the slider.
It feels like drag to a position then stop dragging with/without releasing the mouse button and just after the stop the timer counts to 0.6sec and calls my oxyplot-methods.
The Debounce() method belongs to the namespace CommunityToolkit.WinUI.UI.
<Slider x:Name="PositionSlider" Minimum="0" Maximum="100"></Slider>
PositionSlider.LostMouseCapture += new MouseEventHandler(Position_LostMouseCapture);
PositionSlider.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(Position_DragCompleted));

Silverlight 2 and the MVP Pattern

Any ideas on how i get MVP working with Silverlight? How Do I get around the fact there is no load event raised?
This the view I have:
public partial class Person: IPersonView
{
public event RoutedEventHandler Loaded;
public Person()
{
new PersonPresenter(this);
InitializeComponent();
}
public Person Person
{
set { Person.ItemsSource = value; }
}
}
And my presenter:
public class PersonPresenter
{
private readonly IPersonView _view;
private readonly ServiceContractClient _client;
public PersonPresenter(IPersonView view)
{
_client = new ServiceContractClient();
_view = view;
WireUpEvents();
}
private void WireUpEvents()
{
_view.Loaded += Load;
}
private void Load(object sender, EventArgs e)
{
_client.GetPersonCompleted += Client_GetPerson;
_client.GetPersonAsync();
}
private void Client_GetPerson(object sender, GetPersonCompletedEventArgs e)
{
_view.Person= e.Result;
}
}
Nothing happened for me as the Loaded event dont seem to get called, how do i get around this?
Tim Ross has a good introduction to Silverlight MVP implementation, with source code.
I believe the loaded event gets called when the control has been initialized, loaded, rendered and ready for use. This means that as long as you don't place it inside a visible container (so that it is rendered), the loaded event won't be risen.
You may consider using MVC# - a Model View Presenter framework with Silverlight 2.0 support.
Oleg Zhukov

Resources