Trigger repaint of WPF Button control from external thread - wpf

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.

Related

Update WPF Window Asynchronously

I am creating a simple WPF app that when you click on a button it runs through a few steps like copy the file to a new location, convert the file then it copies the new file back to the original location.
The steps are working fine but I would like to have the WPF window update to which step it is on and hide the button while it is running.
The window only updates once it has finished running my code. I think I used to be able to do this on classic forms with me.refresh but this doesn't work on WPF.
Is something I can do to update the window after each step is complete?
Thank you
Button1.Visibility = Windows.Visibility.Hidden
FileCopy("C:\Test.xsf", AppPath & "\Convert\test.xsf")
Image7.Visibility = Windows.Visibility.Hidden
Image3.Visibility = Windows.Visibility.Visible
Program.StartInfo.FileName = xDefs
Program.StartInfo.Arguments = "/q"
Program.Start()
Program.WaitForExit()
Image5.Visibility = Windows.Visibility.Visible
FileCopy("AppPath & "\Convert\test.csv, "C:\Test.csv")
Button1.Visibility = Windows.Visibility.Visible
In order to update the UI while your program is busy, you'll need to use the Dispatcher class to add your update request onto the UI message queue. Take this synchronous example:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
ConvertFile(filePath);
CopyFileBack();
}
We could use the Dispatcher class to break this up and feed messages back to the UI in between tasks:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied" });
ConvertFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Converted" });
CopyFileBack();
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied back" });
}
private object RunOnUiThread(Action method)
{
return Dispatcher.Invoke(DispatcherPriority.Normal, method);
}
I know this is a VB.NET tagged question but I'll just go ahead and share a C# solution. I hope you know enough of it to port it to VB. This is the first time and posting anything to stackoverflow, if it solves your problem please mark it as the answer :-)
You must first know a thing or two (actually a lot more) on data binding. You basically create a view model, define the property that changes with time and bind this to the window. In this case you must define a value to keep track of the current operation and let the button control.
Disclaimer, I wrote this in notepad and haven't tested it on visual studio. Be on the lookout for typos.
using System.ComponentModel;
namespace FileConverter
{
//define the various states the application will transition to
public enum OperationStatus
{
CopyingFileToNewLocation
ConvertingFile,
CopyingFileToOriginalLocation
OperationCompelete
}
//Defines the view model that shall be bound to the window.
//The view model updates the UI using event notifications. Any control that had enabled
//binding will get updated automatically
public class ViewModel : INotifyPropertyChanged//This interface defines an event used to raise an event and notify subscribers of a changed in data
{
private OperationStatus _FileConvertionStatus;
public event PropertyChangedEventHandler PropertyChanged;
public OperationStatus FileConvertionStatus
{
get
{
return _FileConvertionStatus;
}
set
{
_FileConvertionStatus=value;
//Notify all UIElements / objects that had subscribed to this property that it has changed
RaisePropertyChanged(this,"FileConvertionStatus");
}
}
public void RaisePropertyChanged(object sender,string propertyName)
{
//check if there is any object that had subscribed to changes of any of the data properties in the view model
if(PropertyChanged!=null)
PropertyChanged(sender,new PropertyChangedEventArgs(propertyName));
}
public void StartFileConvertion(string filePath)
{
//Any time we change the property 'FileConvertionStatus', an event is raised which updates the UI
this.FileConvertionStatus=OperationStatus.CopyingFileToNewLocation;
StartCopyingToNewLocation(); //call your copying logic
this.FileConvertionStatus=OperationStatus.ConvertingFile;
StartFileConvertion(); //call your conversion logic
this.FileConvertionStatus=OperationStatus.CopyingFileToOriginalLocation();
CopyFileToOriginalLocation(); //...
this.FileConvertionStatus=OperationStatus.OperationCompelete;
}
}
}
//Now for the UI section
In the constructor of the window, you must bind the window to the view model right after this window has been initialized
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel vm=new ViewModel();
//setting the data context property the window implicitly binds the whole window to our view model object
this.DataContext=vm;
string filePath="c:\file.txt";
//start the file manipulation process
vm.StartFileConvertion(filePath);
}
}
//Next step we need to bind the button to the 'FileConvertionStatus' property located in the view model. We don't bind the button to the whole view model, just the property that it's interested in. Having bound the window to the view model in the previous code, all child elements get access to the public properties of this view model (VM from now on). We do the property binding in XAML
..Button x:Name="btnStartFileProcessing" Enabled="{Binding FileConvertionStatus}"...
We're almost there. One this is missing. You'll notice that the 'Enabled' property is a Boolean value. The 'FileConvertionStatus' property is enum. Same way you can't assign an enum to a Boolean directly, you need to do some convertion. This is where converters come in.
Converters allow you to define how one property can be converted to a different one in XAML. In this case we want the button to be enabled only when file conversion is successful. Please do some reading into this.
Create a class as shown below:
using System.Windows.Data;
namespace FileConverter
{
public class OperationStatusToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
OperationStatus status=(OperationStatus)value;
switch(status)
{
case OperationStatus.OperationCompelete:
return true; //enable the button when everything has been done
default:
return false;//disable the button as the file processing is underway
}
}
public object ConvertBack(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Next step is to define the converter in XAML code. Think of this as initializing it though it can't be further from the true :-). Its more of importing the namespace into the xaml.Put the code below in the App.XAML file. Doing such declaration in the App.XAML file makes the code visible globally.
xmlns:MyConverters="clr-namespace:FileConverter"
In the Application.Resources XAML tag, declare the converter as shown below
<Application.Resources>
<MyConverters:OperationStatusToBooleanConverter x:Key="OperationStatusToBooleanConverter"/>
</Application.Resources>
Final Step
Redo the binding code in the button to include the converter.
...Button Enabled="{Binding FileConvertionStatus,Converter={StaticResource OperationStatusToBooleanConverter}}" x:Name="btnStartFileProcessing" ...
Please note that I haven't thread-optimized this code, the main problem is that all work is being done on the UI thread which can lead to the window hanging if an operation takes long.
The amount of work needed to properly set the binding up as per MVVM code standards is a lot. It might seem like an over-kill and at times, it actually is. Keep this in mind though, once the UI gets complex MVVM will definitely save the day due to the separation of concerns and binding strategies.

Opening a new dialog using WPF with MVVM

I am currently using MVVM (Light) to build an application with WPF. However, in a few cases I must open a new dialog (also WPF) when the user clicks a button. However, this is being a tough fight.
Here is how I am doing it:
private void _ShowItemDialog(Item item)
{
var itemVM = new ItemViewModel();
itemVM.CurrentItem = item ?? new Item();
itemVM.Load();
var itemView = new View.ItemView() { DataContext = itemVM };
if (itemView.ShowDialog() == true)
{
if (item == null)
{
itemList.Add(itemVM.CurrentItem);
}
}
itemVM.Cleanup();
}
And the itemView XAML there is no binding to the DataContext, otherwise two different instances of the ViewModel would be created.
Inside the Window tag. To have the result at ShowDialog, I use the DialogCloser code:
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
In the ItemView, this is declared inside Window tag as follows:
my:DialogCloser.DialogResult="{Binding DialogResult}"
And when the dialog is closed, the closing event sets DialogResult to true or false.
This works perfectly for the first time the screen is opened, but it is not possible to open the dialog again after it is closed.
I would like to know if you have any better ideas for opening the dialog, and why this code does not work.
Thanks!
EDIT:
I have already fixed the code. What I need to do is create a new ViewModel and attach it to the DataContext every time the dialog is opened. Moreover, I had to remove the DataContext binding from XAML. Please check the code changes above.
With these changes I have found out that it is not possible to use the ViewModel from ViewModelLocator because it is a "singleton" and not a new instance at each new window. Therefore, the DialogResult held the last value and if I tried to change its value back to null (as it is when the ViewModel is initialized) an exception is thrown. Do you have any clues of why this happens? It would be very good for me to use the ViewModel from ViewModelLocator, since it would keep the same strategy throughout the system.
Thank you!
I do that by implementing static XxxxInteraction classes that have methods called for example NewUser(); That methods opens the Dialogs and do some work. In my ViewModel I call the XxxxInteraction classes via commands.
The efforts of that way of implementing is, that you can easely modify the methods in the static Interaction classes for using UnitTests.
public static class UserInteractions
{
public static User NewUser()
{
var userDialog = new NewUserDialog();
If(userDialog.ShowDialog() != true) return null;
var user = new User();
user.Name = userDialog.Name;
user.Age = userDialog.Age;
return user;
}
}
public class MyViewModel
{
...
public void NewUserCommandExecute()
{
var newUser = UserInteractions.NewUser();
if(newUser == null) return;
//Do some with new created user
}
}
NewUserDialog is a normal Window that is bound to a ViewModel too.
I think this is a good way of implementing dialogs for the mvvm pattern.
i've done this a while ago, i use a dialog service and call this service in my viewmodel. take a look.
EDIT: btw, thats all you have to do in your viewmodel
var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

How can I get bindings to update when the value is changed?

I'm trying to understand WPF binding. As simple as it gets:
I have a ClassWithProperty that has a public uint Prop1.
The main window has a public ClassWithProp object and uses it for data context. This is set in the main Windows's constructor:
this.ClassWithProp = new ClassWithProp();
this.DataContext = this.ClassWithProp;
ClassWithProp's default constructor sets Porp1 value to 1.
The main windows contains a label:
<Label Content="{Binding Prop1}" ... />
It also contains a button that, when click, sets the ClassWithProp.Prop1 to 2.
When the window first appears, the label correctly shows 1. When the button is clicked the property's value is changed to 2, but the lable does not refresh.
Sorry - probably obvious but I'm a novice in WPF:
Why doesn't the bound label update when the undelying property changes?
Your ClassWithProperty needs to implement the INotifyPropertyChanged interface (which has just the one event on it, PropertyChanged), this way the WPF binding subsystem can listen for property changes and update the value. When you have changed the value of a property, you raise the event.
Here is an example:
pulic class ClassWithProperty : INotifyPropertyChanged
{
public uint Prop1
{
get { return _prop1; }
set
{
_prop1 = value;
OnPropertyChanged("Prop1");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private uint _prop1;
}
Implement INPC.
Also read the overview, it probably answers more than 90% of questions people have about data binding.

Bound Button Not Enabling After Background Worker Process Completes

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.

WPF datagrid multiple windows question

I have a scenario where i load an ICollectionView in a datagrid.
In some cases I modify the data where the collectionview gets it's data from. If I then reload the grid with configGrid.ItemsSource = configData; for example, the data gets updated.
Now the thing is, I sometimes open a new window using:
var newWindow = new Edit(movie);
newWindow.Show();
The thing is, I also edit the data using this new window. Now I want the datagrid in the first window to be refreshed after I close this second window (actually, it doesn't matter when it gets refreshed, as long as it does).
How do I do this?
I might be missing something here (I have a crippling hangover unfortunately) but can't you handle the window closed event of newWindow and refresh confiGrids itemsource there?
Window newWindow = new Window();
newWindow.Closed += new EventHandler(newWindow_Closed);
newWindow.Show();
void newWindow_Closed(object sender, EventArgs e)
{
configGrid.ItemsSource = configData;
}
If the collection behind the ICollectionView supports INotifyCollectionChanged (like ObservableCollection) and the object itself supports INotifyPropertyChanged then the grid is supposed to update automatically
Otherwise you are on your own and the editing window should raise some sort of notification (maybe an event) that you should receive and update the list.
Ok, here's the long version:
WPF data-binding can update the UI automatically - but it needs to know that something changed in order to trigger the update, the easiest way to do this is to support INotifyPropertyChanged, let's create simple class:
public class Movie
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Now, let's add INotifyPropertyChanged support:
public class Movie : INotifyPropertyChanged
{
public event PropertyChanged;
protected virtual OnPropertyChanged(string property)
{
var ev = PropertyChanged;
if(ev!=null)
{
ev(this, new PropertyChangedEventArgs(property));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
Now when you bind to the movie class and change the Name property the UI will be updated automatically.
The next step is to handle a list of Movie objects, we do that by using a collection class the implements INotifyCollectionChanged, luckily for us there's one already written in the framework called ObservableCollection, you user ObservableCollection<T> the same way you would use a List<T>.
So, just bind to ObservableCollection and WPF will automatically detect when objects change or when they are added or removed.
ICollectionView is very useful, it adds support for current item, sorting, filtering and grouping on top of the real collection, if that collection is an ObservableCollection everything will just work, so the code:
ObservableCollection<Movie> movies = new ObservableCollection<Movie>();
ICollectionView view = CollectionViewSource.GetDefaultView(movies);
will give you a collection view that supports automatic change notifications.

Resources