Bound Button Not Enabling After Background Worker Process Completes - wpf

I have a background worker process that starts provisioning a new client for our system. Here is what the DoWork method looks like:
ProvisioningManager manager = new ProvisioningManager(false)
{
};
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
this.MaxSteps = manager.MaxProgress;
}));
manager.StatusUpdated += new ProvisioningManager.StatusUpdatedHandler(manager_StatusUpdated);
manager.TaskCompleted += new ProvisioningManager.TaskCompleteHandler(manager_TaskCompleted);
manager.ProvisionClient();
while (!manager.Completed)
{
System.Threading.Thread.Sleep(100 * 60);
}
Basically it creates the manager that handles talking to the different sub-systems which provision the client.
Now I have a status update event and completed event for the provisioning manager. When the TaskCompleted event fires I want to be able to set a property on my display object so that the finish button in the wizard is enabled:
void manager_TaskCompleted(object sender, ProvisioningManager.Task taskType)
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
this.ProvisioningComplete = true;
}));
}
The XAML for the button looks like this:
<wizard:WizardPage Header="Provisioning Client..."
ShowBack="False"
AllowBack="False"
AllowFinish="{Binding Source={StaticResource ResourceKey=dataObject}, Path=ProvisioningComplete}"
Loaded="Provisioning_Loaded">
</wizard:WizardPage>
This isn't working. Even though I make sure to hit the dispatcher thread to set the property of the display object it doesn't actually change the button to enabled until I click on the window. Is this a bug in AvalonWizard or am I not on the correct thread to set an INotifyPropertyChanged? Is there a way to hack this; basically can I programmatically focus the window without the mouse click?
I tired placing that while loop in the DoWork method so that I could use the BackgroundWorker's completed method:
void provisioningWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
this.ProvisioningComplete = true;
}));
}
That doesn't work either. What gives?!
Update
Here is the requested static resource instantiation for the display object:
<Window.Resources>
<ObjectDataProvider x:Key="dataObject" ObjectType="{x:Type winDO:NewClientWizardDO}" />
</Window.Resources>
Update II
Here is the property and property change firer:
public bool ProvisioningComplete
{
get { return this._ProvisioningComplete; }
set
{
this._ProvisioningComplete = value;
this.NotifyPropertyChanged("ProvisioningComplete");
}
}
protected void NotifyPropertyChanged(params string[] propertyNames)
{
if (this.PropertyChanged != null)
{
foreach (string propertyName in propertyNames)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

sorry if I don't understand something, but is the ProvisioningComplete property marked as "volatile"? If not then this might be the problem.

So I couldn't find out exactly why I was having this issue. I tried setting focus to the window, the button, etc. I tried multiple ways of letting the view know the viewmodel had updated. Basically every suggestion I could find on the web didn't work. It almost seems like a bug.
A smarty on my team suggested faking a mouse click on the window. His idea was that since all it took to activate the button was a simple mouse click on the screen then faking one should have the same effect. I thought (and think) that this hack was ridiculous. I did try it out just to see if I could call it a "solution".
Well, it worked. We had this same problem in another one of our wizards (not AvalonWizard but a homegrown one). I think there has to be some underlying issue with the way the window redraws after a background thread updates objects that are bound to the UI.
Anyhow, the way I found to solve this issue is with the following hack-tastic code.
//import user32.dll and setup the use of the mouse_event method
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
/// <summary>
/// Watches for properties to change on the data object, mainly the ProvisioningComplete method
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void DataObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "ProvisioningComplete":
//if the provisioning is completed then we need to make the finish button selectable.
if (this.DataObject.ProvisioningComplete)
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
//give the window focus
this.Focus();
//update the layout
WizardPageProvisioningClient.UpdateLayout();
//fake mouse click 50 pixels into the window
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, (uint)(this.Left + 50), (uint)(this.Top + 50), 0, 0);
}));
}
break;
}
}
I've tested this when the window is not the active window and when the user leaves the window as selected. The focus method seems to take care of this issue when the window isn't active. Our QA team hasn't run a complete test against the UI so I can't say if there is any situations where it doesn't work, but it seems to be the best solution that I've come up with to date.
I'm open to any other suggestions if anyone out there has a better idea of what could be causing the button to not update.

Related

Execute command after view is loaded WPF MVVM

I have a project based WPF and MVVM.
My project is based on a wizard containing a content control which shows my views (User Controls)
I want to execute a command after the view is loaded completely, I would like the user to see the view UI immediately after the command will be executed.
I tried using :
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding StartProgressCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But the command is executed before I see the view UI and it's not what I'm looking for.
Does anyone have an idea how should I need to implement it?
You could use the Dispatcher for this and set the priority to ApplicationIdle so that it will on execute when everything has finished
Application.Current.Dispatcher.Invoke(
DispatcherPriority.ApplicationIdle,
new Action(() =>
{
StartProgressCommand.Invoke(args);
}));
more information on the dispatcher http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx
cheers.
ste.
That's because even though technically the view is loaded (i.e: all the components are ready in memory), your app is not idle yet, and thus the UI isn't refreshed yet.
Setting a command using interaction triggers on the Loaded event is already good, as there is no better event to attach to.
Now to really wait until the UI is shown, do this in your StartProgress() (I'm assuming here that this is the name of the method that StartProgressCommand point to):
public void StartProgress()
{
new DispatcherTimer(//It will not wait after the application is idle.
TimeSpan.Zero,
//It will wait until the application is idle
DispatcherPriority.ApplicationIdle,
//It will call this when the app is idle
dispatcherTimer_Tick,
//On the UI thread
Application.Current.Dispatcher);
}
private static void dispatcherTimer_Tick(object sender, EventArgs e)
{
//Now the UI is really shown, do your computations
}
another way to do it:
define this xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" and xmlns:mi="http://schemas.microsoft.com/expression/2010/interactions" on your usercontrol XAML and add Microsoft.Expression.Interactions assembly on your project. use CallMethodAction on your trigger, just as bellow:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<mi:CallMethodAction TargetObject="{Binding}" MethodName="StartProgressCommand"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Put the triger inside the root element of your usercontrol, e.g: grid. And change your StartProgressCommand, in your ViewModel class, from command to plain old regular Method, e.g:
public void StartProgressCommand()
{
/* put your program logic here*/
}
It'll run the method exactly one time every time your user control rendered.
We use a the timer solution - i too was very dubious about this but it does seem to work fine.
public static class DispatcherExtensions
{
private static Dictionary<string, DispatcherTimer> timers =
new Dictionary<string, DispatcherTimer>();
private static readonly object syncRoot = new object();
public static void DelayInvoke(this Dispatcher dispatcher, string namedInvocation,
Action action, TimeSpan delay,
DispatcherPriority priority = DispatcherPriority.Normal)
{
lock (syncRoot)
{
RemoveTimer(namedInvocation);
var timer = new DispatcherTimer(delay, priority, (s, e) =>
{
RemoveTimer(namedInvocation);
action();
}, dispatcher);
timer.Start();
timers.Add(namedInvocation, timer);
}
}
public static void CancelNamedInvocation(this Dispatcher dispatcher, string namedInvocation)
{
lock (syncRoot)
{
RemoveTimer(namedInvocation);
}
}
private static void RemoveTimer(string namedInvocation)
{
if (!timers.ContainsKey(namedInvocation)) return;
timers[namedInvocation].Stop();
timers.Remove(namedInvocation);
}
}
Then we invoke using
Dispatcher.CurrentDispatcher.DelayInvoke("InitSomething",()=> {
DoSomething();
},TimeSpan.FromSeconds(1));
You can check IsLoaded property for view, when view is in loaded form it returns false, when view is fully loaded, this property become true.
Thanks,
Rajnikant
Have you tried binding to the ContentRendered event? It will occur after the loaded event, yet I´m not sure whether or not this is a gurantee that the UI thread has finished painting the window then.
You can Write a "Thread.Sleep(10000)" in the first line of "CommandExecute" method. Use the same loaded trigger.
if you don't want to use Thread.Sleep then you can go for "DispatcherTimer". Start a timer in your command execute method and shift all your code to timer tick event.
set your timer interval to 2 seconds, so that user will sen the UI.
I came with this solution for this one.
I wanted to use a boolean property set as true at start of work and to false at the end to allow to notify user of background work.
Basically, it uses
a DispatcherTimer to launch a method after UI render according to this
An async method wich will execute the Action passed as a parameter
Call :
this.LaunchThisWhenUiLoaded(() => { /*Stuff to do after Ui loaded here*/ });
Method :
private DispatcherTimer dispatchTimer;
private Action ActionToExecuteWhenUiLoaded;
/// <summary>
/// Handy method to launch an Action after full UI rendering
/// </summary>
/// <param name="toExec"></param>
protected void LaunchThisWhenUiLoaded(Action toExec)
{
ActionToExecuteWhenUiLoaded = toExec;
// Call UiLoaded method when UI is loaded and rendered
dispatchTimer = new DispatcherTimer(TimeSpan.Zero, DispatcherPriority.ContextIdle, UiLoaded, Application.Current.Dispatcher);
}
/// <summary>
/// Method called after UI rendered
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected async void UiLoaded(object sender, EventArgs e)
{
this.IsBusy = true;
if (ActionToExecuteWhenUiLoaded != null)
await Task.Run(ActionToExecuteWhenUiLoaded);
dispatchTimer.Stop();
this.IsBusy = false;
}
Maybe not the clean but it works as expected.

Button state not updating

I have a small WPF application. This application has a button that when clicked, should have its text changed and be disabled. The code for my button event handler is as follows:
/// <summary>
/// 'Read' button clicked
/// </summary>
/// <param name="sender"></param>
/// <param name="routedEventArgs"></param>
private void ReadVersionNumber(object sender, RoutedEventArgs routedEventArgs)
{
Read.Content = "Reading....";
Read.IsEnabled = false;
SerialPort p = new SerialPort();
string response = "Could not read version";
try
{
// Do some stuff
}
catch (Exception)
{
response = "There was an error while reading the version number";
}
finally
{
Read.IsEnabled = true;
Read.Content = "Read";
if(p.IsOpen)
{
p.Close();
}
}
Version.Text = response;
}
The problem is my button text never changes, and it doesnt become disabled. Ive tried calling UpdateLayout after setting the button properties, but it still doesnt change. The button locks up while it runs through the method, then only updates its layout right at the end of the method. What do i need to do to update the layout?
In the finally (which ALWAYS executes) you set:
Read.IsEnabled = true;
Read.Content = "Read";
Further: if you change a property of a button multiple times in the same function that runs on the UI thread you will not see any changes because the updates will be too fast because the UI thread updates the UI AFTER the method has executed.
If you want to see the changes you could use a backgroundworker and in the ProgressChanged handler update the UI.
It's because you're doing it all on the same thread, so therefore control never returns to WPF so that it can update the state. You need to either split your work onto multiple threads (see BackgroundWorker component) or set the button status using separate messages (see Dispatcher class).

Setting Default Keyboard Focus On Loading A UserControl

I have an MVVM setup with a mainwindow that contains a ContentControl.
I set this to a particular viewmodel which then maps to a view.
A view is a usercontrol.
I want to be able to set the default keyboard focus to a default element in the usercontrol(View) when it loads so the application can eventually be driven just by using up, down, left, right and enter.
Some of my failed attempts are setting
FocusManager.FocusedElement="{Binding ElementName=DefaultElement}"
in my content control tag. This sets the logical focus but not the keyboard focus
I'd rather keep the solution in xaml if possable but have tried placing the following in code behind.
Keyboard.Focus(DefaultElement);
This does not work but if I popup a message box first it does. I'm a little confused as to why.
MessageBox.Show(Keyboard.FocusedElement.ToString());
Keyboard.Focus(DefaultElement);
EDIT::::
I just placed this in my onloaded event of my user control. It seems to work but can anyone see any issues that might arrise at this priority level. I.E a circumstance when the action will never run?
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
Keyboard.Focus(DefaultElement);
}));
It seems that this wpf the you have to implement a workaround on a case by case basis. The solution that seemed to work best, most of the time for me was to insert the focus code inside the dispatcher when OnVisible was changed. This sets the focus not only when the View/Usercontrol loads but also if you a changing Views by way of Visibility. If you Hide and then Show a ContentControl that is mapped to your ViewModels then the Loaded event won't fire and you'll be forced to Mouse input, or tabbing (Not so good if you want to navigate your app with a remote control).
VisibilityChanged will always fire however. This is what I ended up with for my listbox.
private void ItemsFlowListBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == true)
{
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
ItemsFlowListBox.Focus();
ItemsFlowListBox.ScrollIntoView(ItemsFlowListBox.SelectedItem);
}));
}
}
I had the same symptom for a WPF UserControl hosted in a Winforms application. Just wanted to note I was about to try this solution when I found a normal TabIndex in the Winforms app fixed it
Per How to set which control gets the focus on application start
"The one with the minimum tab index automatically gets the focus
(assuming the TabStop property is set to true). Just set the tab
indices appropriately."
It's a tricky one with no easy answer. I'm currently doing this, although I'm not sure I like it:
public MyView()
{
InitializeComponent();
// When DataContext changes hook the txtName.TextChanged event so we can give it initial focus
DataContextChanged +=
(sender, args) =>
{
txtName.TextChanged += OnTxtNameOnTextChanged;
};
}
private void OnTxtNameOnTextChanged(object o, TextChangedEventArgs eventArgs)
{
// Setting focus will select all text in the TextBox due to the global class handler on TextBox
txtName.Focus();
// Now unhook the event handler, since it's no longer required
txtName.TextChanged -= OnTxtNameOnTextChanged;
}
And in case you're wondering what the global class handler does, it's this:
protected override void OnStartup(StartupEventArgs e)
{
...
// Register a global handler for this app-domain to select all text in a textBox when
// the textBox receives keyboard focus.
EventManager.RegisterClassHandler(
typeof (TextBox), UIElement.GotKeyboardFocusEvent,
new RoutedEventHandler((sender, args) => ((TextBox) sender).SelectAll()));
which auto selects TextBox text when receiving keyboard focus.

Trigger repaint of WPF Button control from external thread

I am having some issues with WPF not fully repainting a button control when the button is changed from another thread, and I am not sure how to force it to do a full repaint.
The situation is that on receipt of a message (via WCF - but the source isn't important, except that it is an external thread) I update the foreground color and visibility of a button. WPF immediately repaints the text on the button face, but the surface of the button is not repainted until I click anywhere on the application.
I have tried calling InvalidateVisual() on the button, but that did not help. I think that I am not understanding how a background thread can force a repaint. But the frustrating thing is that something is getting repainted and every other control I am using (text and image controls) are also getting properly repainted when I update them from my same message receipt.
I have now tried sending an empty message to the Dispatcher of the application via Invoke(), but no luck there either.
So I am looking for tips on how to tell WPF that it needs to update the rest of the button and not just the text.
Edit
This is a rough skeleton of my program. Note that I have wrapped the button in a class as there is other related state information I am keeping with it.
class myButton
{
Button theButton
void SetButton()
{
theButton.Forground = a new color
}
}
main
{
myButton.theButton = (Button on WPF canvass)
RegisterCallback( mycallbackFunction) with WCF client endpoint
}
void myCallbackFunction(message)
{
if message has button related stuff, call myButton.SetButton
}
Edit 2
Solved my problem .. it was actually a conflict between a "CanExecute" method and setting the buttons attributes in the callback. Once I removed the "CanExecute" function it all worked.
Setting properties on the button itself from code, especially another thread/callback, is an entrance to a painful world of inconsistent states.
What you should do is bind your button's properties to properties in your code, and then have your callback change those external properties.
I know the code you posted was kind of a mock up for what you actually want to do in your program, and I couldn't really follow your logic, but here's a complete program that operates similarly to your example and shows what I'm talking about. Let me know if I've missed the mark.
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public class MyButton : INotifyPropertyChanged
{
private Button _theButton;
public Button TheButton
{
get { return _theButton; }
set
{
_theButton = value;
//set text binding
Binding textBind = new Binding("Text");
textBind.Source = this;
textBind.Mode = BindingMode.OneWay;
_theButton.SetBinding(Button.ContentProperty, textBind);
//set color binding
Binding colorBind = new Binding("Brush");
colorBind.Source = this;
colorBind.Mode = BindingMode.OneWay;
_theButton.SetBinding(Button.ForegroundProperty, colorBind);
NotifyPropertyChanged("TheButton");
}
}
public void Set(string text, Brush brush)
{
this.Text = text;
this.Brush = brush;
}
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged("Text"); }
}
private Brush _brush;
public Brush Brush
{
get { return _brush; }
set { _brush = value; NotifyPropertyChanged("Brush"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
internal void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public partial class MainWindow : Window
{
MyButton _myButton = new MyButton();
public MainWindow()
{
InitializeComponent();
//button1 is defined in XAML markup
_myButton.TheButton = this.button1;
//or else this could be your callback, same thing really
Thread t = new Thread(SetButton);
t.Start();
}
void SetButton()
{
_myButton.Text = "wo0t!";
_myButton.Brush = Brushes.Red;
//or
_myButton.Set("giggidy!", Brushes.Yellow);
}
}
}
Note that binding your Button properties in XAML is much less ugly, but then we're getting into UserControls and DataContexts which is another topic. I would look at inheriting the Button class to implement the features you want.
I recommend reading the article (Build More Responsive Apps With The Dispatcher) from MSDN magazine that describes how WPF works with the Dispatcher when using BackgroundWorker.
As per my edit, I had conflict between the buttons CanExecute binding in the XAML and me setting the background color in the callback. I didn't really need the CanExecute, so getting rid of that solved my problem.

WPF ComboBox DropDown part appears in the wrong place

I put several ComboBoxes on a XAML window. When I expand any of them, the DropDown part appears on the upper left corner of the screen.
I use Visual Studio 2008 C# Express. I don't remember this phenomenon when I used Visual Studio 2008 (Trial Version), though I use the same FrameWork (3.5).
It seems to be a bug.
Workaround:
Use Window.Show() instead with a custom logic to simulate the ShowDialog() behavior.
This appears to be a bug in WPF. In my case, I was trying to open a window in the Loaded event of another window. To get around this, I set a timer up to fire, then used a delegate to open the window (cannot open the window in a timer event because the calling thread that opens a window must be STA).
Edit - timer isn't necessary - didn't see the answer above just queue it on the dispatcher...
private delegate void DelegateOpenWindow();
private DelegateOpenWindow m_DelegateOpenWindow;
private Timer loginTimer = new Timer(200);
private void MainWindow1_Loaded(object sender, RoutedEventArgs e)
{
// create delegate used for asynchronous call
m_DelegateOpenWindow= new DelegateOpenWindow(this.OpenWindow);
// start a timer to fire off the open window.
loginTimer.Elapsed += loginTimer_Elapsed;
loginTimer.Enabled = true;
}
void loginTimer_Elapsed(object sender, ElapsedEventArgs e)
{
loginTimer.Enabled = false;
this.Dispatcher.BeginInvoke(m_DelegateOpenWindow);
}
void OpenWindow()
{
MyWindow w = new MyWindow();
w.Owner = this;
w.ShowDialog();
}
I started observing this (and other strange behavioral quirks) yesterday when I tried to "tweak" window sizes, shapes, colors, and invoke a log-on dialog from the Window.Loaded event handler. I had been doing this just fine in each of a dozen+ individual "MVVM" pattern apps. Yesterday, I decided to move this from each app's code behind into a consolidated code-behind base class, since the pre-processing had become common in all those apps. When I did, the drop-downs in two ComboBoxes in the log-in dialog suddenly appeared in the upper left corner of my screen. I seem to have "solved" it by using the following technique (your mileage may vary):
protected void WindowBaseLoadedHandler(object sender, RoutedEventArgs e)
{
...non-essential lines of code removed...
if (DataContext != null)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
/*----------------------------------------------------------------------
* Do we have a View Model? If so, perform standard VM Initialization...
*---------------------------------------------------------------------*/
this.IsEnabled = false;
LoginDlg loginDlg = new LoginDlg();
loginDlg.ShowDialog();
if (!loginDlg.Success)
{
/*-----------------------------------
* Log on failed -- terminate app...
*----------------------------------*/
...termination logic removed...
}
this.IsEnabled = true;
}));
}
WindowBaseLoadedHandler is the Loaded event handler. LoginDlg is a WPF app with a dialog containing two ComboBoxes.
Recap: After I consolidated the code into the Loaded event handler of the base class the ComboBox's drop down lists appeared in the upper left corner of my screen. Once I wrapped the logic into the Dispatcher.BeginInvoke call, the appropriate ComboBox behavior returned with lists below the current item.
I suspect WPF needs the application to return from the Loaded event to complete the layout system's initialization. That doesn't fully explain why it worked before, but I'll have to queue up my desire to hunt that "why" down for some rainy day in the future and celebrate overcoming the latest obstacle for today.
In any event, I hope someone finds this of use.
I'm using the latest .Net 4.5 and WPF framework and I still have this problem. One thing I noticed is that it only happen when there's an attached debugger. When the debugger is not attached, everything works fine.
I had the same problem on Visual Studio 2019.
Using window.Show() can help but it can ruin your design.
The solution is to open the window asynchronously.
var yourDialog= new YourDialog();
yourDialog.Owner = this;
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>();
this.Dispatcher.BeginInvoke(new Action(() =>
completion.SetResult(yourDialog.ShowDialog())));
bool? result = await completion.Task;
You can also create a more elegant solution by making the extension method:
public static class AsyncWindowExtension
{
public static Task<bool?> ShowDialogAsync(this Window self)
{
if (self == null) throw new ArgumentNullException("self");
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>();
self.Dispatcher.BeginInvoke(new Action(() => completion.SetResult(self.ShowDialog())));
return completion.Task;
}
}
And you can use it like this:
await dlgReview.ShowDialogAsync();
It’s a bug in WPF (not the only one, I'm afraid). It happened when I opened another window in the Loaded Event, something like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Window selectionWindow = new SelectionWindow();
bool? result = selectionWindow.ShowDialog();
if (result == true)
RecordChanged();
}
I already found a workabout.

Resources