I've created a busy indicator - basically an animation of a logo spinning. I've added it to a login window and bound the Visibility property to my viewmodel's BusyIndicatorVisibility property.
When I click login, I want the spinner to appear whilst the login happens (it calls a web service to determine whether the login credentials are correct). However, when I set the visibility to visible, then continue with the login, the spinner doesn't appear until the login is complete. In Winforms old fashioned coding I would have added an Application.DoEvents. How can I make the spinner appear in WPF in an MVVM application?
The code is:
private bool Login()
{
BusyIndicatorVisibility = Visibility.Visible;
var result = false;
var status = GetConnectionGenerator().Connect(_model);
if (status == ConnectionStatus.Successful)
{
result = true;
}
else if (status == ConnectionStatus.LoginFailure)
{
ShowError("Login Failed");
Password = "";
}
else
{
ShowError("Unknown User");
}
BusyIndicatorVisibility = Visibility.Collapsed;
return result;
}
You have to make your login async. You can use the BackgroundWorker to do this. Something like:
BusyIndicatorVisibility = Visibility.Visible;
// Disable here also your UI to not allow the user to do things that are not allowed during login-validation
BackgroundWorker bgWorker = new BackgroundWorker() ;
bgWorker.DoWork += (s, e) => {
e.Result=Login(); // Do the login. As an example, I return the login-validation-result over e.Result.
};
bgWorker.RunWorkerCompleted += (s, e) => {
BusyIndicatorVisibility = Visibility.Collapsed;
// Enable here the UI
// You can get the login-result via the e.Result. Make sure to check also the e.Error for errors that happended during the login-operation
};
bgWorker.RunWorkerAsync();
Only for completness: There is the possibility to give the UI the time to refresh before the login takes place. This is done over the dispatcher. However this is a hack and IMO never should be used. But if you're interested in this, you can search StackOverflow for wpf doevents.
You can try to run busy indicar in a separate thread as this article explains: Creating a Busy Indicator in a separate thread in WPF
Or try running the new BusyIndicator from the Extended WPF Toolkit
But I'm pretty sure that you will be out of luck if you don't place the logic in the background thread.
Does your login code run on the UI thread? That might block databinding updates.
Related
I'm writing a WPF control that contains an ItemsControl. The control adds and removes items based on certain user actions. Once an item has been added, the control needs to access a FrameworkElement inside the ItemTemplate instance that was just created.
I'm using ItemContainerGenerator.ContainerFromIndex to do this. I also get a ContentPresenter back, but it is empty: it appears it takes a few milliseconds on a separate thread to instantiate the template objects.
I read that I need to use ItemContainerGenerator.Status to determine whether or not the containers are fully created, so I wrote the following method:
private async Task<TextBox> GetMainInputControl(int index)
{
// _selectedItemsEditor is the ItemsControl inside my main control that contains the items
var evt = new ManualResetEvent(false);
_selectedItemsEditor.ItemContainerGenerator.StatusChanged += (sender, args) =>
{
var status = _selectedItemsEditor.ItemContainerGenerator.Status;
if (status == GeneratorStatus.ContainersGenerated || status == GeneratorStatus.Error)
{
evt.Set();
}
};
ContentPresenter container = null;
await Task.Run(() =>
{
var status = _selectedItemsEditor.ItemContainerGenerator.Status;
if (status == GeneratorStatus.GeneratingContainers
|| status == GeneratorStatus.NotStarted)
{
evt.WaitOne();
}
container =
_selectedItemsEditor.ItemContainerGenerator.ContainerFromIndex(index) as ContentPresenter;
});
return container?.ContentTemplate.FindName("PART_ItemEditorMainInput", container) as TextBox;
}
I know that there are a few things I need to fix here, but mostly, it just doesn't work, because _selectedItemsEditor.ItemContainerGenerator.Status immediately returns GeneratorStatus.ContainersGenerated, so the code doesn't wait - but then the code container?.ContentTemplate.FindName throws an exception indicating that the container is NOT ready.
How can I make this work, or alternatively use a better way of achieving this?
That code looks like you're trying to access ui controls on a background thread. So I'm not at all surprised it doesn't work.
There are two approaches I would consider.
You could defer your code so it waits until the dispatcher ( the ui thread essentially ) has done it's stuff for whatever you just asked it to do.
Application.Current.Dispatcher.InvokeAsync(new Action(() =>
{
// Your code which is to run after the items are rendered
}), DispatcherPriority.ContextIdle);
Or
You could force the layout process so you make the items do their thing. This will potentially lock the ui up whilst it's working. If the user clicks something and his obvious intent is to wait for layout to update or there's not so much going on then this won't be a problem.
You could just call .UpdateLayout() on your control.
https://msdn.microsoft.com/en-us/library/system.windows.uielement.updatelayout(v=vs.110).aspx
I'm using C# VS2008, WinForm application
I have a checkedlistbox control on my form (win-form application)
In the code I check some items in checkedlistbox using the SetItemChecked(index, false) method and it raise the event ItemCheck.
I also allow the user to check items in that checkedlistbox and it also raise the event ItemCheck when the user check or uncheck an item.
How can I find in the ItemCheck event how this event occur (via code or via user keyboard/mouse input)?
Thanks.
I think that there is no a simple way to differentiate the situation using code.
The only thing that comes to mind is through the use of a global form variable:
public class Form1:Form
{
bool _isCodeClick = false;
.....
// Somewhere in your code
_isCodeClick = true;
checkedListBox1.SetItemChecked(index, true);
_isCodeClick = false;
.....
private void CheckedListBox1_ItemCheck(Object sender, ItemCheckEventArgs e)
{
if(_isCodeClick == true)
{
// Do processing for click by code
}
else
{
// Do processing for click by user
}
}
}
If you go for this solution remember to take additional steps to correctly trap exceptions that could bypass the reset of the global variable to the false state.
Probably using advanced manipulation of keyboard and mouse events you could reach a reasonable way to identify what has caused the ItemCheck event, but sometime some solutions are too complex and not worth it.
EDIT: Reviewing my answer I feel the need to add a little change to reduce the maintaining problems that this esponse implies.
The code that set the boolean variable and call the SetItemChecked should be encapsulated in a separate function like this
private void SetItemCheckedFromCode(int index, bool toSet)
{
try
{
_isCodeClick = true;
checkedListBox1.SetItemChecked(index, true);
}
finally
{
_isCodeClick = false;
}
}
In my wpf application, I have a menu. When I click on one of the elements of the menu, I change my screen data, which is quite a long process.
I tried to disable the main window when I do such a loading, using this method :
private void SetNavigation(MainContentTypeEnum enumVal, int id, ICheckState vm)
{
var parent = Window.GetWindow(this);
var tmpCursor = parent.Cursor;
parent.Cursor = Cursors.Wait;
parent.IsEnabled = false;
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += (o, args) =>
{
try
{
Dispatcher d = args.Argument as Dispatcher;
d.Invoke(new Action(() =>
{
Navigation.Navigator.SetContol(enumVal, id, vm);
}));
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
};
bw.RunWorkerCompleted += (o, args) =>
{
parent.IsEnabled = true;
parent.Cursor = tmpCursor;
};
bw.RunWorkerAsync(Dispatcher.CurrentDispatcher);
}
This method works on the very first call, the form is disabled, and then enabled when data is loaded. But on next calls, it doesn't work anymore, everything freezes until the operation completes. I tried setting a breakpoint, and the method is correctly hit and executed. I don't understant why it only works one time...
Have you an idea ?
Thanks in advance!
Edit: A bit of precision: this code is part of a usercontrol, which is why I call the parent using Window.GetWindow(this);
Edit2: Setting a Thread.Sleep(1000); just before invoking the dispatcher does the job. My guess is that the parent.IsEnabled instruction is not executed quickly enough... but why ?
Edit3: Having made some timings, my data retrieval is quite quick. It seems that the problem exists on the binding phase. I set the value to the bound property, and the method returns. However, the UI still frozen for a moment after that.
I have an application all done in WPF, using MvvM/Prism/Unity and Remote as datasource.
I need a basic thing that on win forms is really easy, just check if the app is iddle after few minutes , and if is idle, lock the app and show the login screen.
After some search on google I ´ve found one solution that uses DllImport and another using pure Wpf methods.
I don´t know I , after I implemented the Wpf way (pls check the code below) it only works after I login into the app , if I open and click in a simple texbox or hit a search, the idle method is not fired, looks like there is something hanged in the background that makes Wpf idle routine to think that it´s doing something when it´s not.
How can I check all the services/methods/etc.. that are in memory related to may app ? callstack doesn´t show to much for me. I am affraid that or I am not calling in the correct way the remote services or I implemented something wrong on the props PropChanged events/observablecollections/etc...
Is there a better way to do this using 100% Wpf structure ?
private void CheckIdleTime()
{
handler = delegate
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(5);
timer.Tick += delegate
{
if (timer != null)
{
timer.Stop();
timer = null;
System.Windows.Interop.ComponentDispatcher.ThreadIdle -= handler;
Console.WriteLine("IDLE! Lets logoff!");
this.LockApplication();
Console.WriteLine("logoff fired");
System.Windows.Interop.ComponentDispatcher.ThreadIdle += handler;
}
};
timer.Start();
Dispatcher.CurrentDispatcher.Hooks.OperationPosted += delegate
{
if (timer != null)
{
timer.Stop();
timer = null;
}
};
};
ComponentDispatcher.ThreadIdle += handler;
}
There will be default window events find the idle time... i think it will be wise if we use same events for wpf or any other applications...
following link will help you to implement it..
Application.Idle event not firing in WPF application
Is there a way to log all of the clicks in a Win Forms application? I'd like to intercept clicks and record the action and the name of the control that caused it.
Is this possible?
Thanks in advance.
UPDATE: I'm looking for an application wide solution, is there no way to add a listener to the windows event queue (or what ever it is called)?
You can do this by having your app's main form implement the IMessageFilter interface. You can screen the Window messages it gets and look for clicks. For example:
public partial class Form1 : Form, IMessageFilter {
public Form1() {
InitializeComponent();
Application.AddMessageFilter(this);
this.FormClosed += (o, e) => Application.RemoveMessageFilter(this);
}
public bool PreFilterMessage(ref Message m) {
if (m.Msg == 0x201 || m.Msg == 0x203) { // Trap left click + double-click
string name = "Unknown";
Control ctl = Control.FromHandle(m.HWnd);
if (ctl != null) name = ctl.Name;
Point pos = new Point(m.LParam.ToInt32());
Console.WriteLine("Click {0} at {1}", name, pos);
}
return false;
}
}
Note that this logs all clicks in any window of your app.
You could use Spy++ or WinSpy++ to achieve this.
alt text http://www.catch22.net/sites/default/files/images/winspy1.img_assist_custom.jpg
But I'm not sure how you can achieve the same thing yourself. If it's possible you'd need to do it via a low-level Windows API hook or a message handler that gives you access to all the message in your applications queue.
Well, you could subscribe to the Click or MouseDown event of every control on the form.
use MouseEventArgs like this:
private void Form_MouseDown(object sender, System.WinForms.MouseEventArgs e)
{
switch (e.Button)
{
case MouseButtons.Left:
MessageBox.Show(this,"Left Button Click");
break;
case MouseButtons.Right:
MessageBox.Show(this,"Right Button Click" );
break;
case MouseButtons.Middle:
break;
default:
break;
}
EventLog.WriteEntry("source", e.X.ToString() + " " + e.Y.ToString()); //or your own Log function
}
The NunitForms test project has a recorder application that watches for this and many other events. The code is very clever and worth a good look. It's a ThoughtWorks project.
That's the rolls Royce solution though!...
Try recursively walking the Controls collection of the form and subscibe to the event based on the type.
PK :-)