Wait cursor in WPF between PropertyChanged and Bind - wpf

I have a WPF App and I'm using MVVM.
In my view model I have:
private string logs;
public string Logs
{
get { return logs; }
set
{
logs = value;
OnPropertyChanged("Logs");
}
}
private void ExecLoadData()
{
using (new WaitCursor())
Logs = LogFile.ReturnContent();
}
private RelayCommand loadData;
public ICommand LoadData
{
get
{
if (loadData == null)
loadData = new RelayCommand(param => this.ExecLoadData());
return loadData;
}
}
In View:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadData}" />
</i:EventTrigger>
</i:Interaction.Triggers>
I'm noticing that between the shooting of the OnPropertyChanged and presentation of data on the page occurs a delay.
I need a way to display the wait cursor to the data to be displayed on the screen.
Already implemented the method WaitCursor() but the wait cursor only appears until the data file is loaded into memory, that is, between the loading of data in memory until the data is displayed on the page the cursor remains normal.
Any tips?
Edit (Final solution with help of AngelWPF):
private Boolean isBusy = false;
public Boolean IsBusy
{
get { return isBusy; }
set
{
if (isBusy == value)
return;
isBusy = value;
OnPropertyChanged("IsBusy");
}
}
private string logs;
public string Logs
{
get { return logs; }
set
{
logs = value;
OnPropertyChanged("Logs");
}
}
public void ExecuteBusy(DoWorkEventHandler doWorkEventHandler)
{
IsBusy = true;
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += doWorkEventHandler;
backgroundWorker.RunWorkerCompleted += (sender, e) => { IsBusy = false; };
backgroundWorker.RunWorkerAsync();
}
protected override void ExecLoadData()
{
LoadLogs();
}
private void LoadLogs()
{
ExecuteBusy((sender, e) =>
{
Logs = LogFile.ReturnContent();
});
}
<Page.Resources>
<ut:BooleanVisibilityConverter x:Key="BooleanVisibilityConverter" />
</Page.Resources>
<Page.DataContext>
<vm:ManutencaoMonitoracaoLogsViewModel/>
</Page.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadData}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<TextBox Text="{Binding Logs, Mode=OneWay}" VerticalScrollBarVisibility="Auto" IsReadOnly="True" BorderBrush="White" />
<Border BorderBrush="Black" BorderThickness="1" Background="#80DBDBDB" Grid.RowSpan="3"
Visibility="{Binding IsBusy, Converter={StaticResource BooleanVisibilityConverter}}">
<Grid>
<ct:LoadingAnimation HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Border>
</Grid>

You shouldn't have any reference to your cursor in a model implementation, how do you call from UI the ExecelLoadData() method? I suggest to change cursor status before to make the call and rechange when it returned

This needs orchestrating any heavy functionalities via something we call as AsyncWorker. This is a asynchrnous command execution using background worker. It has a one time trigger flag that is initiated from the view model as true, so that it runs in the adorner of your window when any heavy functionality is delegated to it. When the functionality is executing the animation indicates the user that a possibly delayed functionality is running and he/she should wait. Then when the delegate finishes, AsyncWorker itself hides the animation and displays the Page properly back to user.
http://elegantcode.com/2009/08/21/a-simple-wpf-loading-animation/
I can envision it can be done this way...
Characteristics of `AsyncWorker`
1. It is a Control that runs an animation such as
a neverending progressing progress bar
or rotating circles etc. in the adorner of the UI.
2. It accepts the parent panel on which the waiter animation is shown.
3. It has a boolean dependency property say "StartOperation".
When this changes to true we start the animation.
When true this also begins excuting the `WorkerDelegateCommand` given below.
4. It also has a ICommand dependency property called "WorkerDelegateCommand"
This will be supplied from your `ViewModel`.
It will hold the time consuming operation as a delegate.
So basically when we set AsyncWorker.StartOperation to true, we render the adorner of the parent panel with an animating storyboard and kick off a background worker. This background worker runs the WorkerDelegateCommand on a different thread. Thus your slow operation runs on a another thread than UI. meanwhile the async worker animation keeps running. When the WorkerDelegateCommand delegate finishes its slow work, the background woker DoWork call exits and RunCompleted is called. In this we set StartOperation to false.
The way we can configure this AsyncWorker is this way...
<Grid>
<Button Content="Do some slow work"
Command="{Binding RunAsyncWorkerCommand}" />
<AsyncWorker
ParentPanel="{Binding RelativeSource={RelativeSource
AncestorType={x:Type Grid}}}"
StartOperation="{Binding StartSlowWork, Mode=TowWay}"
WorkerDelegateCommand="{Binding MySlowDelegateCommand}"
Visibility="Collapsed" />
</Grid>
So in above example, when the buton is clicked the grid, that contains the button, shows a waiter animation and begins to perform the slow operation. For this your DataContext and / or ViewModel needs three properties...
1. `StartSlowWork` - A boolean flag to start AsyncWorker which is TwoWay bound.
2. `RunAsyncWorkerCommand` - Command to set the `StartSlowWork` flag to true.
3. `MySlowDelegateCommand` - Command to execute slow work.
Once you have this in place, every slow executing operations can be moved to AsyncWorker.

Related

WPF and MVVM. Display and hide a label with a timeout

in my MVVM application, I wish to create an auto-closing popup to notify some information to the users (for example "data changes saved successfully").
so, I placed a label into the form, bound to a VM property. Then, I wish to set my message and cancel it after a delay (1 second). But it seems not to work. the app just wait some time, and shows the final status (ie: when the user push "save" button, the app "waits" for one second, and then the label is empty).
any ideas to get it? thanks
Why can't you use normal popup in WPF
<Popup Margin="10,10,0,13" Name="Popup1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="194" Height="200" IsOpen="True">
<StackPanel>
<TextBlock Name="McTextBlock"
Background="LightBlue" >
This is popup text
</TextBlock>
<Button Content="This is button on a Pupup" />
</StackPanel>
public void show()
{
Popup1.IsOpen = true;
Thread t = new Thread(hide);
t.Start();
}
private void hide() {
Thread.Sleep(5000);
Popup1.IsOpen = false;
}
call show function when you want to show popup

MVVM: Change button content and command binding at runtime

I have a button which should be used as Connect or Disconnect button, depending on the Connected-property of the ViewModel:
<Button Content="_Connect" x:Name="connectButton" Command="{Binding ConnectCommand}"/>
Now depending on the property, the content should be either "_Connect" or "_Disconnect" and the command binding should go either to ConnectCommand or DisconnectCommand.
Is there a nice way of doing that or should I use a command for both and have a DataTrigger to set the content separately depending on the Connected property?
Thanks a lot!
Using same command you can handle this with enum.
<Button Name="btnOption"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="8"
Command="{Binding RxOptionCommand}"
Content="{Binding RxOptionContent}" />
Based on your View Model's connect or disconnect business change the button content and at the same time set your enum value in to a variable.
public enum EnumRxRecStatus
{
None = 0,
New = 1,
}
Now you can check the condition in to your command event
public void OnRxOptionCommand(object sender)
{
if (RequestForRxOption == EnumRxRecStatus.None)
{
// Do something
}
else if (RequestForRxOption == EnumRxRecStatus.New)
{
// Do something
}
}
It is indeed very simple:
As Clemens suggested, I ended up setting the Binding and the Contents with a DataTrigger.

Master-detail in XAML with - managing ListView selection through binding/commands whilst allowing cancellation

I'm attempting to build a WPF application for demonstration that follows the best possible MVVM practices, and I've found quickly that I'm not sure what the best practices are! :)
I have a specific issue right now.
As some background (in case it shows in my snippets), I'm using MVVMLight (for its PCL portability), NInject as a container and Prism for its region support.
View
[snippet]
<!-- bind the selection of a new item to the view model -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectTypeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<!-- visual template of the list items -->
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock>Name: <Run Text="{Binding Name}"></Run></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- detail view -->
<Grid Grid.Row="0" Grid.Column="1">
<StackPanel Orientation="Vertical" Margin="5">
<Label>ID</Label>
<TextBox Text="{Binding SelectedType.Id, Mode=TwoWay}" IsEnabled="False"></TextBox>
<Label>Name</Label>
<TextBox Text="{Binding SelectedType.Name, Mode=TwoWay}"></TextBox>
</StackPanel>
</Grid>
ViewModel
[snippet]
public class ClientTypesViewModel : BaseUpdateableViewModel
{
private ThingType selectedtype = null;
public ThingType SelectedType
{
get { return selectedtype; }
protected set { Set(() => SelectedType, ref selectedtype, value); }
}
private RelayCommand<ThingType> selecttypecommand;
public RelayCommand<ThingType> SelectTypeCommand
{
get { return selecttypecommand ?? (selecttypecommand = new RelayCommand<ThingType>(ExecuteSelectTypeCommand)); }
}
private async void ExecuteSelectTypeCommand(ThingType newtype)
{
// Save the type if required - goes away to a service (HttpClient...)
if (!await SaveSelectedType())
{
// Cancel navigation?
return;
}
// Update the selected type
this.SelectedType = newtype;
}
private async Task<bool> SaveSelectedType()
{
if (selectedtype == null) return true;
if (!selectedtype.IsDirty) return true;
bool? result = await navigationservice.AskConfirmation("Do you want to save this client type?", "Yes", "No", "Cancel");
if (result == null)
return false; // cancel
if (!result.Value)
{
selectedtype.MakeClean();
return true; // ignore changes
}
// Ask the data service to save for us
await dataservice.UpdateClientType(selectedtype);
selectedtype.MakeClean();
return true;
}
}
Two columns, left hand holds a list of entities that when one is selected, the details column on the right updates to allow view/edit. If an entity is edited by the user in the right panel I mark its view model "dirty".
When the user tries to select a different entity in the left column, I'd like to be able to (in the ViewModel) ask the user if they want to navigate away and lose their changes, or if they'd like to save them.
This I can present (through a navigation service), but I'm at a loss as to how to actually make the "cancel" work in the view model that is making me rethink my whole approach.
If I was binding the SelectedItem in both directions then I think I could just not update the underlying field before the RaisePropertyChanged is fired - but as I would need to call into async code to persist my entity (HttpClient) I can't do that from within a property setter.
So I've gone with the above that I can't really get working without what feels like horrible hacks.
Is there a better general solution to this that I'm just not seeing? Or even better an example out there?
EDIT
I've realised that its not clear what I'm trying to accomplish here. The issue is not how to revert changes made, as #Alan has rightly pointed out there are some standard ways to deal with that.
I'm asking the user the following on an attempt to change the selected type in the left pane:
Do you want to save this type?
[Yes] - save and allow navigation
[No] - revert changes and allow navigation
[Cancel] - keep changes and cancel navigation
Its [Cancel] that I have no idea how to handle properly.
I believe your answer is in the Prism book by implementing IConfirmNavigationRequest?
Confirming or Cancelling Navigation
You will often find that you will need to interact with the user during a navigation operation, so that the user can confirm or cancel it. In many applications, for example, the user may try to navigate while in the middle of entering or editing data. In these situations, you may want to ask the user whether he or she wants to save or discard the data that has been entered before continuing to navigate away from the page, or whether the user wants to cancel the navigation operation altogether. Prism supports these scenarios via the IConfirmNavigationRequest interface.
From the Prism book
8: Navigation

Mvvm Light & EventToCommand - Textbox LostFocus firing twice

I have a few textboxes on a form that, when focus is lost, I'd like to call a setter stored procedure to save the data, then in my callback function call a getter stored proc which will update a job costing summary on my form. I'm using Mvvm light & when I try & bind an EventToCommand on a LostFocus EventTrigger, my command is fired twice.
I understand this is due to event bubbling, but I'm not sure how to make sure my method is only actually fired once. Here's my xaml:
<TextBox x:Name="txtMiles" Grid.Row="1" Width="80" Grid.Column="2" Margin="2" Text="{Binding Miles, Mode=TwoWay}" HorizontalAlignment="Center" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<cmd:EventToCommand Command="{Binding UpdateJobCost}" CommandParameter="{Binding Text, ElementName=txtMiles}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
And my ViewModel:
public RelayCommand<string> UpdateJobCost { get; set; }
public WorkOrderControlViewModel(TSMVVM.Services.IWorkOrderService workOrderService)
{
WorkOrderService = workOrderService;
RegisterCommands();
LoadData();
}
private void RegisterCommands()
{
UpdateJobCost = new RelayCommand<string>((value) => updateJC(value));
}
private void updateJC(string value)
{
//Handle Setter service call here
}
Many thanks,
Scott
I haven't seen that problem before with EventToCommand. There might be something funky in your app that's causing the problem.
In general I don't rely on the UI to do the right thing. If updateJC shouldn't execute before a previous call has finished, consider adding an "isUpdatingJC" flag in your class. Only update the JC when the flag is false, and set it to true before you get started with the update. That way you don't get in a tight spot because some UI has issues.
Hope that helps...
Cheers!
The problem wasn't with updateJC firing async and not being complete when it fires again. I want it to only fire once. I ended up just creating a class for this form which contained a property for each of the fields. Whenever I update the property, I call updateJC which gathers the object & sends along for processing

Change ToggleButton/RadioButton state only on external event

I want to present several ToggleButton/RadioButton elements that:
Map to an enumeration, meaning the DataContext has a "public Mode CurrentMode" property.
Are mutually exclusive (only one button is checked)
When a button is clicked, the state doesn't change immediately. Instead, a request is sent to a server. The state changes when the response arrives.
Have a different image for checked/unchecked state
For example, 4 buttons would display the following view-model:
public class ViewModel
{
public enum Mode { Idle, Active, Disabled, Running }
Mode m_currentMode = Mode.Idle;
public Mode CurrentMode
{
get { return m_currentMode; }
set
{
SendRequest(value);
}
}
// Called externally after SendRequest, not from UI
public void ModeChanged(Mode mode)
{
m_currentMode = mode;
NotifyPropertyChanged("CurrentMode");
}
}
My initial approach was to use the solution from How to bind RadioButtons to an enum?, but that is not enough since the button state change immediately, even if I don't call NotifyPropertyChanged in the setter. In addition, I don't like the "GroupName" hack.
Any ideas? I don't mind creating a custom button class, as I need many buttons like that for multiple views.
I'm using .NET 3.5 SP1 and VS2008.
If you want to use the RadioButtons you just need to make some minor tweaks to workaround the default behavior of the RadioButton.
The first issue you need to workaround is the automatic grouping of RadioButtons based on their common immediate parent container. Since you don't like the "GroupName" hack your other option is to put each RadioButton inside of its own Grid or other container. This will make each button a member of its own group and will force them to behave based on their IsChecked binding.
<StackPanel Orientation="Horizontal">
<Grid>
<RadioButton IsChecked="{Binding Path=CurrentMode, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Idle}">Idle</RadioButton>
</Grid>
<Grid>
<RadioButton IsChecked="{Binding Path=CurrentMode, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Active}">Active</RadioButton>
</Grid>
<Grid>
<RadioButton IsChecked="{Binding Path=CurrentMode, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Disabled}">Disabled</RadioButton>
</Grid>
<Grid>
<RadioButton IsChecked="{Binding Path=CurrentMode, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Running}">Running</RadioButton>
</Grid>
</StackPanel>
This brings me to the next workaround which is ensuring the button clicked on doesn't stay in its Checked state after clicking on it which was needed in order to trigger the set call because you are binding on the IsChecked property. You will need to send out an additional NotifyPropertyChanged, but it must be pushed into the queue of the Dispatch thread so the button will receive the notification and update its visual IsChecked binding. Add this to your ViewModel class, which is probably replacing your existing NotifyPropertyChanged implementation and I am assuming your class is implementing the INotifyPropertyChanged which is missing in the question's code:
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
Dispatcher uiDispatcher = Application.Current != null ? Application.Current.Dispatcher : null;
if (uiDispatcher != null)
{
uiDispatcher.BeginInvoke(DispatcherPriority.DataBind,
(ThreadStart)delegate()
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}
}
}
Then in your CurrentMode's Setter call NotifyPropertyChanged("CurrentMode"). You probably already needed something like this since your Server's ModeChanged call is probably coming in on a thread that isn't the Dispatcher thread.
Finally you will need to apply a Style to your RadioButtons if you want them to have a different Checked/Unchecked look. A quick Google search for WPF RadioButton ControlTemplate eventually came up with this site: http://madprops.org/blog/wpf-killed-the-radiobutton-star/.

Resources