Blocking UI on click in Caliburn.Micro, WPF - c#

How to block UI in Caliburn.Micro?
public async void AcceptButton()
{
await this.httpDataService.Register(account);
Show.SuccesBox(alert);
this.TryClose();
}
How to wait for end of Task in my ViewModel by blocking View?
EDIT
i added binding on my button in xaml:
IsEnabled="{Binding isEnabled}"
Then, my VM:
bool isEnabled {get;set;}
public async void AcceptButton()
{
this.isEnabled = false;
await this.httpDataService.Register(account);
Show.SuccesBox(alert);
this.TryClose();
}
In this case, AcceptButton is always unactive IsEnabled=false. How to trigger false only on button click?

because i dont want to user double-tap "Send form" button
The standard way of doing that is just disabling the button. Or, if you want to disable the entire form, then disable the entire window:
public async void AcceptButton()
{
this.IsEnabled = false;
await this.httpDataService.Register(account);
Show.SuccesBox(alert);
this.TryClose();
}
You should add the IsEnabled property to your view model class and then bind it to the IsEnabled property of the view.
Disabling the window is nicer to the user than an unresponsive application, because this approach allows the user to minimize it or move it out of the way if it is taking an unexpectedly long time.

Related

Can a ContentView receive focus?

I have an element that I want to become highlighted when navigated to with a D-pad and respond to an "enter" button. I assumed I could do this with a focused element and am trying to set up my ContentView to handle such interaction.
The ContentView class inherits from VisualElement and has a Focus() method. The documentation states that for the Focus() method to work, the "[element] must be able to receive focus."
How can I make an element "able to receive focus"? It doesn't appear to work in UWP or Tizen with this bare-bones example. Tabbing, clicking, arrow keys, programmatically setting the focus... Nothing sets IsFocused to true, and my callback is never called.
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MyProject.Components
{
public class FocusMe : ContentView
{
public FocusMe ()
{
Content = new StackLayout {
Children = {
new Label { Text = "Welcome to Xamarin.Forms!" }
}
};
Focused += FocusMe_Focused;
Task.Run(async () => {
await Task.Delay(5000);
Focus(); // false
_ = IsFocused; // false
});
}
private void FocusMe_Focused(object sender, FocusEventArgs e)
{
// never called
}
}
}
Can a ContentView receive focus?
Base on the testing, ContentView will not receive Focused event. However, Frame layout could receive focused event, and frame inherits from ContentView, you could use Frame to replace. Please note, you need to place focusable control in it such as button.
If you work on Tizen TV, How about check TV.UIControls, it is a extension library for TV applications
If do you want to react a key event on a view, I recommend to use ContentButton view on TV.UIControls
It is based on ContentView but works like a button
Here is a guide
https://samsung.github.io/Tizen.TV.UIControls/guides/ContentButton.html

How to process only last Task and discard previous in c#

I have a WPF application. Window includes datagrid for showing data and button for searching them. In viewmodel there is a method for loading data to the datagrid.
public List<MyObject> DataGridData { get; set; }
public void LoadDataToDatagrid(object obj)
{
Task.Run(() =>
{
DataGridData = new List<MyObject>(repository.GetData());
});
}
Everytime, when i click the button to load the data, the data is loaded to datagrid, but it takes a long time. So when i click the button more than once, the first data are loaded. I will start working with them and then new data are loaded again.
How can I stop previous tasks and load data only for last click button?
Thanks for your answers
You could maybe disable the button until the content is loaded?
You would have to set your button disabled property to have a dependency on a bool in your ViewModel and then set it to true when you are adding to the datagrid and then set it to false when you are done.
To do this, I am assuming you can raise events of some kind of:
NotifyPropertyChanged();
In XAML you would do something like this:
<Button IsEnabled="{Binding CanClose}"/>
Where CanClose is a bool in your ViewModel. When you change the state of the bool, you have to raise some kind of event. You can also disable the button first, then start filling the datagrid and when finished, enable it again?
Solved:
List<CancellationTokenSource> tokens = new List<CancellationTokenSource>();
public List<MyObject> DataGridData { get; set; }
public void LoadDataToDatagrid(object obj)
{
foreach (var token in tokens)
token.Cancel();
CancellationTokenSource tokenSource = new CancellationTokenSource();
tokens.Add(tokenSource);
var task = Task.Run(() =>
{
return repository.GetData();
}, tokenSource.Token);
try
{
DataGridData = await task;
}
finally
{
tokenSource.Dispose();
tokens.Remove(tokenSource);
}
}
That code show only data from last method call withou freeze application.
Thanks for answer Dmitry Bychenko.

How can I cancel an Await in a WPF dialog when the user clicks the close button with MVVM?

I have an application that requires a user to enter SQL connection information in a dialog. When the user clicks the "Connect" button, I await the result of SqlConnection.OpenAsync() with the supplied credentials in order to test that they are valid before closing the dialog box. I also disable the Connect button to avoid duplicate attempts. When the credentials are correct, it returns almost immediately, but when they are not, it can take up to 30 seconds to return. The cancel button is able to use a CancellationToken to cancel the request and close the dialog.
The problem is that the user is still able to click the window's close button on the dialog to dismiss it. My view model doesn't get notified, but the form is still closed. 30 seconds or so later the connection attempt returns with error information, and shows a message box.
Is there a good, MVVM friendly way, to cancel the connection attempt with my CancelationToken when the form is closed in this way? I know that I could rig up something using in the dialog's code-behind, but I'd like to avoid referencing the view model from there.
Depending on what you are using for your MVVM infrastructure, you could use something like MVVM Light's EventToCommand on the Window.Closing. You may bind this event to the cancellation command that you have attached to the button.
See MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet? for a way to do this.
Try awaiting the result of SqlConnection.OpenAsync() and pass in a cancellation token.
You can write an extension-method therefor.
So you have a class (let's call it WindowExtensions) where your attached property is definded.
internal class WindowExtensions
{
public static readonly DependencyProperty WindowClosingCommandProperty = DependencyProperty.RegisterAttached(
"WindowClosingCommand", typeof (ICommand), typeof (WindowExtensions), new PropertyMetadata(null, OnWindowClosingCommandChanged));
private static void OnWindowClosingCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window window = d as Window;
if (window == null)
return;
if (e.NewValue != null)
{
window.Closing += WindowOnClosing;
}
}
private static void WindowOnClosing(object sender, CancelEventArgs e)
{
Window window = sender as Window;
if (window == null)
return;
ICommand windowClosingCommand = GetWindowClosingCommand(window);
windowClosingCommand.Execute(e);
}
public static void SetWindowClosingCommand(DependencyObject element, ICommand value)
{
element.SetValue(WindowClosingCommandProperty, value);
}
public static ICommand GetWindowClosingCommand(DependencyObject element)
{
return (ICommand) element.GetValue(WindowClosingCommandProperty);
}
}
In your XAML on the Window-Element you can map your attached-property to an ICommand-Property in your ViewModel like:
nameSpaceOfWindowExtensions:WindowExtensions.WindowClosingCommand="{Binding WindowClosingCommand}"
And in your ViewModel you have a ICommand-Property where you can handle it.
Something like:
private ICommand windowClosingCommand;
public ICommand WindowClosingCommand
{
get { return windowClosingCommand ?? (windowClosingCommand = new RelayCommand(OnWindowClosing)); }
}
private void OnWindowClosing(object parameter)
{
CancelEventArgs cancelEventArgs = parameter as CancelEventArgs;
if (cancelEventArgs != null)
{
// If you want to cancel the closing of the window you can call the following:
//cancelEventArgs.Cancel = true;
}
}
If you don't need the CancelEventArgs in your ViewModel, just modify the following line in the attached-property:
windowClosingCommand.Execute(e);
to
windowClosingCommand.Execute(null);

Binding FocusAction Behavior on Button Click

I am not very experienced using Behaviors, but so far they have come in handy to be able to execute code from the ViewModel but still triggering the actions from the View.
In my current scenario, I have a TextBox that appears when a Button is clicked. I would like to then set the focus of that TextBox after the Button has been clicked.
Previously, I have been able to set the focus using an EventTriggerBehavior like so:
<core:EventTriggerBehavior>
<behaviors:FocusAction />
</core:EventTriggerBehavior>
However, this will only suffice if I want to set the focus of that control when the View is loaded. In this case, the TextBox isn't visible at that time, and actually, the focus goes to a different TextBox initially.
Is there a way to set the focus of a control from the ViewModel? This is a WinRT 8.1 application, but will be being ported to Windows 10 Universal in the future.
EDIT
The answer here looks like it would do what I am looking for, but when I try it I get an error:
Cannot resolve symbol 'UIPropertyMetadata'
From what I can tell, that class exists in the System.Windows namespace, but even with a using System.Windows; I still get the same error. I have also tried new System.Windows.UIPropertyMetadata(null, ElementToFocusPropertyChanged) as well, but that doesn't make a difference either. Is that class not available in WinRT?
I was able to get it to work by slightly modifying the answer linked to in my original question. For anyone looking to accomplish this in a WinRT application, here is the modified code:
public class EventFocusAttachment
{
public static Control GetElementToFocus(Button button)
{
return (Control)button.GetValue(ElementToFocusProperty);
}
public static void SetElementToFocus(Button button, Control value)
{
button.SetValue(ElementToFocusProperty, value);
}
public static readonly DependencyProperty ElementToFocusProperty =
DependencyProperty.RegisterAttached("ElementToFocus", typeof(Control),
typeof(EventFocusAttachment), new PropertyMetadata(null, ElementToFocusPropertyChanged));
public static void ElementToFocusPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
button.Click += async (s, args) =>
{
var control = GetElementToFocus(button);
if (control != null)
{
await Task.Delay(100);
control.Focus(FocusState.Programmatic);
}
};
}
}
}
I had to add a small delay because the TextBox that I am focusing on is not visible until after the button is pressed, so it wasn't working without the delay. Also, I had to change the UIPropertyMetadata to simply PropertyMetadata, and add async to the lambda expression to allow for the await Task.Delay(100);

WPF Please Wait Dialog

I'm developing a WPF desktop application in c# 4.0 which has to handle a lot of long-running operations (loading data from the DB, calculating simulations, optimizing routes, etc.).
When these long-running operations run in the background I want to show a Please-Wait dialog. When the Please-Wait dialog is shown the application should be locked, but to just disable the application window isn't a good idea because all the DataGrids would lose their status (SelectedItem).
What I have so far works but there are some problems:
A new WaitXUI is created using the Create-factory method. The Create method expects caption text and a refernce to the host control that should be locked. The Create method sets the StartupLocation of the window, the caption text and the host to lock:
WaitXUI wait = WaitXUI.Create("Simulation running...", this);
wait.ShowDialog(new Action(() =>
{
// long running operation
}));
With the overloaded ShowDialog method the WaitXUI can then be displayed. The ShowDialog overload does expect an Action which wraps the long running operation.
In the ShowDialog overload I just start the Action in its own thread and then disable the host control (set Opacity to 0.5 and set IsEnabled to false) and call ShowDialog of the base class.
public bool? ShowDialog(Action action)
{
bool? result = true;
// start a new thread to start the submitted action
Thread t = new Thread(new ThreadStart(delegate()
{
// start the submitted action
try
{
Dispatcher.UnhandledException += Dispatcher_UnhandledException;
Dispatcher.Invoke(DispatcherPriority.Normal, action);
}
catch (Exception ex)
{
throw ex;
}
finally
{
// close the window
Dispatcher.UnhandledException -= Dispatcher_UnhandledException;
this.DoClose();
}
}));
t.Start();
if (t.ThreadState != ThreadState.Stopped)
{
result = this.ShowDialog();
}
return result;
}
private new bool? ShowDialog()
{
DisableHost();
this.Topmost = true;
return base.ShowDialog();
}
private void DisableHost()
{
if (host != null)
{
host.Dispatcher.Invoke(new Action(delegate()
{
this.Width = host.Width - 20;
host.Cursor = Cursors.Wait;
host.IsEnabled = false;
host.Opacity = 0.5;
}));
}
}
Here are the problems with this:
Disabling the host control results in lost of status information (SelectedItems...)
The WaitXUI sometimes is shown for only some milliseconds when the thread ends a few ms after the WaitXUI is shown
Sometimes the dialog doesn't appear at all although the thread is still running
These are the main problems which come to my mind at the moment. How can this concept be improved, or what other methods can be employed to address this problem?
Thanks in advance!
A little lateral thinking always helps when developing WPF applications. You can fulfil your requirements easily with just a Grid, a Rectangle, a bool property (which you could already have) and a BooleanToVisibilityConverter and you won't have to disable any controls.
The idea is simple. Add a white Rectangle in front of your view content with its Opacity property set between 0.5 and around 0.75. Data bind its Visibility property to the bool property in your view model or code behind and plug in the BooleanToVisibilityConverter:
<Grid>
<Grid>
<!--Put your main content here-->
</Grid>
<Rectangle Fill="White" Opacity="0.7" Visibility="{Binding IsWaiting,
Converter={StaticResource BooleanToVisibilityConverter}}" />
<!--You could add a 'Please Wait' TextBlock here-->
</Grid>
Now when you want to disable the controls, you just set the bool property to true and the Rectangle will make the UI appear faded:
IsWaiting = true;
Don't really need to create own implementation, I think it's redundant.
take a look into already created component, like BusyIndicator, for similar needs. which is vital and effective. .
more info from codeplex

Categories

Resources