I have a WPF ItemsControl displaying a series of Rectangles.
Each rectangle makes use of MVVM Lights EventToCommand to track the MouseEnter event and set the Rectangle to 'Selected'
I then use this property to highlight the rectangle using triggers in the style.
My problem occurs if the mouse is dragged too quickly.
Working (Slowly dragged):
Not working (quickly dragged):
In this case the event has not fired for the second Rectangle.
How do I make sure the event fires for all controls the mouse moves over?
My suggestion would be to put all the event occurences in a queue. I think your problem occures because when an event fires, you call one method and if the method hasn't finished at the time the next event occurs, it can not be called a second time.
Create a queue and put the event occures in there, then create a thread that waits for the queue to fill and then procceses it it.
Sample:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ConsoleApplication3
{
using System.Timers;
using Timer = System.Timers.Timer;
class Program
{
private static Queue<My_Event> EventQueue;
private static Timer t = new Timer(10);
static void Main(string[] args)
{
Task thread = new Task(MarkStuffYellow);
EventQueue = new Queue<My_Event>();
t.Elapsed += t_Elapsed;
t.Start();
t.AutoReset = true;
}
static void t_Elapsed(object sender, ElapsedEventArgs e)
{
EventQueue.Enqueue(new My_Event(sender, e));
}
private static void MarkStuffYellow()
{
/// !!! While (true) is not an solution, if you do it like this,
/// your CPU will be full !!!
while (true)
{
if (EventQueue.Any())
{
My_Event myEvent = EventQueue.Dequeue();
var sender = myEvent.Sender;
var e = myEvent.E;
/// Do Stuff with your event
}
}
}
}
/// Needed to save your event
internal class My_Event
{
public My_Event(object sender, ElapsedEventArgs e)
{
this.Sender = sender;
this.E = e;
}
public object Sender;
public ElapsedEventArgs E;
}
}
Related
I'm trying to improve the responsiveness of a WPF business application so that when users are "between" screens waiting for a new screen to appear after a server response, they can still be entering data. I'm able to queue the events (using a PreviewKeyDown event handler on background panel) but then I'm having difficulties just throwing the events I dequeue back at the new panel once it's loaded. In particular TextBoxes on the new panel are not picking up the text. I've tried raising the same events (setting Handled to true when capturing them, setting Handled to false when raising them again) creating new KeyDown events, new PreviewKeyDown events, doing ProcessInput, doing RaiseEvent on the panel, setting the focus on the right TextBox and doing RaiseEvent on the TextBox, many things.
It seems like it should be really simple, but I can't figure it out.
Here are some of the things I've tried. Consider a Queue of KeyEventArgs called EventQ:
Here's one thing that doesn't work:
while (EventQ.Count > 0)
{
KeyEventArgs kea = EventQ.Dequeue();
tbOne.Focus(); // tbOne is a text box
kea.Handled = false;
this.RaiseEvent(kea);
}
Here's another:
while (EventQ.Count > 0)
{
KeyEventArgs kea = EventQ.Dequeue();
tbOne.Focus(); // tbOne is a text box
var key = kea.Key; // Key to send
var routedEvent = Keyboard.PreviewKeyDownEvent; // Event to send
KeyEventArgs keanew = new KeyEventArgs(
Keyboard.PrimaryDevice,
PresentationSource.FromVisual(this),
0,
key) { RoutedEvent = routedEvent, Handled = false };
InputManager.Current.ProcessInput(keanew);
}
And another:
while (EventQ.Count > 0)
{
KeyEventArgs kea = EventQ.Dequeue();
tbOne.Focus(); // tbOne is a text box
var key = kea.Key; // Key to send
var routedEvent = Keyboard.PreviewKeyDownEvent; // Event to send
this.RaiseEvent(
new KeyEventArgs(
Keyboard.PrimaryDevice,
PresentationSource.FromVisual(this),
0,
key) { RoutedEvent = routedEvent, Handled = false }
);
}
One strange thing I've noticed is that when using the InputManager method (#2) spaces do appear. But normal text keys do not.
The same resources turned up for me when I did some research, so I think what you do in your answer is pretty valid.
I looked on and have found another way of doing it, using the Win32 API. I had to introduce some threading and small delays, because for some reason the key events were not replayed in the correct sequence without that. Overall I think this solution is easier though, and I also figured out how to include modifier keys (by using the Get/SetKeyboardState function). Uppercase is working, and so should keyboard shortcuts.
Starting the demo app, pressing the keys 1 space 2 space 3 tab 4 space 5 space 6, then clicking the button produces the following:
Xaml:
<UserControl x:Class="WpfApplication1.KeyEventQueueDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" >
<StackPanel>
<TextBox x:Name="tbOne" Margin="5,2" />
<TextBox x:Name="tbTwo" Margin="5,2" />
<Button x:Name="btn" Content="Replay key events" Margin="5,2" />
</StackPanel>
</UserControl>
Code behind:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
namespace WpfApplication1
{
/// <summary>
/// Structure that defines key input with modifier keys
/// </summary>
public struct KeyAndState
{
public int Key;
public byte[] KeyboardState;
public KeyAndState(int key, byte[] state)
{
Key = key;
KeyboardState = state;
}
}
/// <summary>
/// Demo to illustrate storing keyboard input and playing it back at a later stage
/// </summary>
public partial class KeyEventQueueDemo : UserControl
{
private const int WM_KEYDOWN = 0x0100;
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
[DllImport("user32.dll")]
static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
static extern bool SetKeyboardState(byte[] lpKeyState);
private IntPtr _handle;
private bool _isMonitoring = true;
private Queue<KeyAndState> _eventQ = new Queue<KeyAndState>();
public KeyEventQueueDemo()
{
InitializeComponent();
this.Focusable = true;
this.Loaded += KeyEventQueueDemo_Loaded;
this.PreviewKeyDown += KeyEventQueueDemo_PreviewKeyDown;
this.btn.Click += (s, e) => ReplayKeyEvents();
}
void KeyEventQueueDemo_Loaded(object sender, RoutedEventArgs e)
{
this.Focus(); // necessary to detect previewkeydown event
SetFocusable(false); // for demo purpose only, so controls do not get focus at tab key
// getting window handle
HwndSource source = (HwndSource)HwndSource.FromVisual(this);
_handle = source.Handle;
}
/// <summary>
/// Get key and keyboard state (modifier keys), store them in a queue
/// </summary>
void KeyEventQueueDemo_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (_isMonitoring)
{
int key = KeyInterop.VirtualKeyFromKey(e.Key);
byte[] state = new byte[256];
GetKeyboardState(state);
_eventQ.Enqueue(new KeyAndState(key, state));
}
}
/// <summary>
/// Replay key events from queue
/// </summary>
private void ReplayKeyEvents()
{
_isMonitoring = false; // no longer add to queue
SetFocusable(true); // allow controls to take focus now (demo purpose only)
MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); // set focus to first control
// thread the dequeueing, because the sequence of inputs is not preserved
// unless a small delay between them is introduced. Normally the effect this
// produces should be very acceptable for an UI.
Task.Run(() =>
{
while (_eventQ.Count > 0)
{
KeyAndState keyAndState = _eventQ.Dequeue();
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
SetKeyboardState(keyAndState.KeyboardState); // set stored keyboard state
PostMessage(_handle, WM_KEYDOWN, keyAndState.Key, 0);
}));
System.Threading.Thread.Sleep(5); // might need adjustment
}
});
}
/// <summary>
/// Prevent controls from getting focus and taking the input until requested
/// </summary>
private void SetFocusable(bool isFocusable)
{
tbOne.Focusable = isFocusable;
tbTwo.Focusable = isFocusable;
btn.Focusable = isFocusable;
}
}
}
The enqueue system is something that I've wanted to do myself, as part of my project which allows multi-threaded UI to function without any problems(one thread routes events into another). There is only slight problem, namely WPF does not have public API to inject INPUT events. Here is a copy/paste from one of the Microsoft employees that I talked with, like weeks back:
"WPF does not expose public methods for injecting input events in the proper way. This scenario is just not supported by the public API. You will probably have to do a lot of reflection and other hacking. For example, WPF treats some input as “trusted” because it knows it came from the message pump. If you just raise an input event, the event will not be trusted."
I think you need to rethink your strategy.
Thanks all for your support but I haven't really struck a solution from the SO community so I'm going to answer this myself since this is the closest I seem to get to a solution. The "hack" as Erti-Chris says seems to be what we're left with. I've had some luck decomposing the problem so I don't have the sense I'm writing a whole new keyboard handler. The approach I'm following is to decompose the events into a combination of InputManager handling and of TextComposition. Throwing a KeyEventArgs (either the original one or one I've created myself) doesn't seem to register on a PreviewKeyDown handler.
Part of the difficulty comes from the information in Erti-Chris's post, and another part seems to be related to TextBoxes trying to react to certain keys like arrow keys differently from normal keys like the letter "A".
To move forward with this I found information from this post to be useful:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b657618e-7fc6-4e6b-9b62-1ffca25d186b
Here is the solution that I'm getting some positive results from now:
Keyboard.Focus(tbOne); // the first element on the Panel to get the focus
while (EventQ.Count > 0)
{
KeyEventArgs kea = EventQ.Dequeue();
kea.Handled = false;
var routedEvent = KeyDownEvent;
KeyEventArgs keanew = new KeyEventArgs(
Keyboard.PrimaryDevice,
PresentationSource.FromVisual(tbOne),
kea.Timestamp,
kea.Key) { RoutedEvent = routedEvent, Handled = false };
keanew.Source = tbOne;
bool itWorked = InputManager.Current.ProcessInput(keanew);
if (itWorked)
{
continue;
// at this point spaces, backspaces, tabs, arrow keys, deletes are handled
}
else
{
String keyChar = kea.Key.ToString();
if (keyChar.Length > 1)
{
// handle special keys; letters are one length
if (keyChar == "OemPeriod") keyChar = ".";
if (keyChar == "OemComma") keyChar = ",";
}
TextCompositionManager.StartComposition(new TextComposition(InputManager.Current, Keyboard.FocusedElement, keyChar));
}
}
If anyone can show me a better way I'm delighted to mark your contribution as the answer, but for now this is what I'm working with.
I'm in trouble with a Marquee ProgressBar. I need to execute a method (refreshList()) to get a List<string>. Then I assign this List to a ComboBox, so ComboBox refreshes with the new Items. As refreshList() take 3 or 4 sec, I wanted to run a Marquee ProgressBar. But I couldn't. ProgressBar is ok, but ComboBox doesn't load new Items.
My refreshList() method:
private void refreshList(List<string> list)
{
albumList.DataSource = null;
albumList.DataSource = list;
}
I have the following code, it works fine:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
refreshList(N.getList(folderPath));
}
}
But I added a ProgressBar and wrote this code:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
bgWorker.WorkerReportsProgress = true;
bgWorker.RunWorkerAsync();
}
}
And I placed refreshList() in doWork() method:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
refreshList(N.getList(folderPath));
}
But unfortunately this isn't working. Can anybody help me solving this problem? Thanks in advance.
You can use the MarqueeAnimationSpeed and Value properties of the ProgressBar control to stop and start the Marquee. There's no need to use WorkerReportsProgress* as you aren't incrementing a normal progress bar - you just want to "spin" the Marquee.
You can do something like the following:
public Form1()
{
InitializeComponent();
//Stop the progress bar to begin with
progressBar1.MarqueeAnimationSpeed = 0;
//If you wire up the event handler in the Designer, then you don't need
//the following line of code (the designer adds it to InitializeComponent)
//backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
//This line effectively starts the progress bar
progressBar1.MarqueeAnimationSpeed = 10;
bgWorker.RunWorkerAsync(); //Calls the DoWork event
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = N.getList(folderPath); //Technically this is the only work you need to do in the background
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//these two lines effectively stop the progress bar
progressBar1.Value = 0;
progressBar1.MarqueeAnimationSpeed = 0;
//Now update the list with the result from the work done on the background thread
RefreshList(e.Result as List<String>);
}
private void RefreshList(List<String> results)
{
albumList.DataSource = null; //You don't need this line but there is no real harm.
albumList.DataSource = list;
}
Remember to wire up the RunWorkerCompleted event to backgroundWorker1_RunWorkerCompleted via the Properties bar, Events section in the designer.
To begin with, we start the ProgressBar's animation by setting the MarqueeAnimationSpeed property to a non-zero positive number as part of your successful folder selection.
Then, after calling RunWorkerAsync, the code builds your list in the DoWork method, then assigns the result to the DoWorkEventArgs, which get passed to the RunWorkerCompleted event (which fires when DoWork is finished).
In the backgroundWorker1_RunWorkerCompleted method, we stop the progress bar (and set it's value to zero to effectively return it to it's original state), and then we pass the list to the refreshList method to databind it and populate the ComboBox.
Tested using VS2012, Windows Forms, .Net 4.0 (with a Thread.Sleep to emulate the time taken for N.getList)
*WorkerReportsProgress, and the associated ReportProgress method/event are used when you want to increment the progress bar - you can tell the GUI that you are 10% done, 20% done, 50% done etc etc.
I've noticed that there is a difference in the time it takes for a WPF Progress Bar and a WinForms Progress Bar to fill completely.
Fill completely as in set the Value to 100 in both Forms and WPF, one can notice that WinForms fills the bar smoothly whereas the WPF fills it instantly.
I wanted to know if there is a property that we can edit in the templates to change that.
Hope I made it clear, I can post a video too if anyone wants.
EDIT
Here's a video of what I'm talking about, notice the difference ?
EDIT 2
Filling the progress bar with a timer ?
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.Title = "WPF Progress Bar Demo";
}
private void fill(int from, int to)
{
Duration duration = new Duration(TimeSpan.FromSeconds(0.5));
DoubleAnimation doubleanimation = new DoubleAnimation(from, to, duration);
progb.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void fill_Click(object sender, RoutedEventArgs e)
{
fill(0, 100);
}
}
}
Is that OK and will it work anywhere ?
Feel free to change it.
Thanks.
The idea is that a progress bar reports actual progress - not time elapsed. It's not intended to be an animation that just indicates something is happening.
The basic principle is that you bind Value to a property on your DataContext class, and update that value whenever a progress milestone occurs.
You can make it fill at a specified rate using a timer - here is an example:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ProgressBar Value="{Binding Path=ProgressValue}"></ProgressBar>
</Grid>
And the code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
Timer timer;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
timer.Start();
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
if (this._progressValue < 100)
this.ProgressValue = _progressValue + 10;
else
{
timer.Stop();
timer.Dispose();
}
}
private double _progressValue;
public double ProgressValue
{
get { return _progressValue; }
set
{
_progressValue = value;
RaisePropertyChanged("ProgressValue");
}
}
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
See my answer on How to update a progress bar so it increases smoothly?
It's similar to the extension method, but uses a behavior so that you can decouple the progress bar from the thing that's reporting progress. :)
It looks like it's a problem (or not) with only WPF progress bar...another user reported it here
WPF Control, exactly progress bar, does not update itself when
copying When I test to copy a big file, the complete GUI just
completely freezes. The progress bar doesn’t run smoothly. It just
jumps from 0 to 100.
It was solved by adding an extension method:
//Your Code
pbBar.Value = some_value;
pbBar.Refresh();
//Your Code
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
public static void RefreshInput(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Input, EmptyDelegate);
}
}
Calling the Refresh() method after setting the value solved the issue.
But, what I found was even after applying the refresh() method, the progress bar jumps on each run (from different values).
Using a backgroundworker and reportprogress gives the exact result with no "jumps".
I've made a rather complex Silverlight 4 out-of-browser application. One of my main view models adds an event handler to the Application.Current.MainWindow.Closing event.
This works fine when the application is initially run. It is able to cancel the close operation.
However, sometimes after performing operations like showing and closing a ChildWindow, the MainWindow's Closing event is no longer calling my handler.
In the debugger, I added a watch to the MainWindow's underlying closing event delegate. It's not null before showing the ChildWindow. Then sometimes after the ChildWindow is closed the delegate is null. This is explains why my handler is not called any more. But why is this delegate getting nulled? And why is it only happening occasionally?
My application is not unbinding my event handler at any point.
This is the delegate I'm watching:
System.Windows.Application.Current.MainWindow.m_closingEvent
Other stuff: I'm using Caliburn Micro
I had the exact same problem. We have a large silverlight application running OOB.
For some reason the m_ClosingEvent was nulled after running for a while. I have not been able to find the cause of this issue but I think it may have something to do with us changing the root visual or all the child windows we show.
I´m using a class ApplicationWrapper.
public class ApplicationWrapper : IApplicationWrapper
{
public void Initialize()
{
HookCloseEvent(true);
}
private void HookCloseEvent(bool hook)
{
if (hook && IsRunningOutOfBrowser)
{
Application.Current.MainWindow.Closing += OnClosing;
}
else
{
if (IsRunningOutOfBrowser)
{
Application.Current.MainWindow.Closing -= OnClosing;
}
}
}
private void OnClosing(object sender, ClosingEventArgs e)
{
InvokeClosing(e);
}
... etc..
}
And the InvokeClosing method was never called. But when I changed it to
public class ApplicationWrapper : IApplicationWrapper
{
private Window _mainWindow;
public void Initialize()
{
if(IsRunningOutOfBrowser)
{
_mainWindow = Application.Current.MainWindow;
}
HookCloseEvent(true);
}
private void HookCloseEvent(bool hook)
{
if (hook && IsRunningOutOfBrowser)
{
_mainWindow.Closing += OnClosing;
}
else
{
if (IsRunningOutOfBrowser)
{
_mainWindow.Closing -= OnClosing;
}
}
}
private void OnClosing(object sender, ClosingEventArgs e)
{
InvokeClosing(e);
}
... etc...
}
The m_ClosingEvent isn´t nulled.
So, try to just store the "initial" MainWindow in a field and check if that solves your problem.
Instead of hooking to the event, why not register a service instead? Create a class that implements IApplicationService and IApplicationLifetimeAware. The latter gives you an "onexiting" and "onexited" pair of events. You place the service in the application by pointing to it in a section called in your App.xaml. I've used this for many projects and never had an issue with the exiting methods not being called.
Ok, after pulling out my hair and many false starts I finally found the answer - it seems to be a known bug with the Closing event, OOB and ChildWindows open/closes...
The trick is to store a static reference to the Main Window:
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
//you have to store this to work around the bug
//http://forums.silverlight.net/forums/p/185664/424174.aspx
_mainWindow = App.GetApp.MainWindow;
App.GetApp.MainWindow.Closing += (s, e1) =>
{
if (UIUtilities.ShowMessage("Would you like to exit AMT Mobile?", "Exit Application", MessageBoxButton.OKCancel) != MessageBoxResult.OK)
{
e1.Cancel = true;
}
};
}
Is there an event in WinForms that get's fired when a window is dragged?
Or is there a better way of doing what I want: to drop the window opacity to 80% when the window is being dragged around?
Unfortunately this is stupidly tricky to search for because everyone is looking for drag and drop from the shell, or some other object.
No need for WndProc hacking, this works fine:
protected override void OnResizeBegin(EventArgs e) {
this.Opacity = 0.6;
}
protected override void OnResizeEnd(EventArgs e) {
this.Opacity = 1.0;
}
Moves also trigger the OnResizeXxx events.
It's the LocationChanged event you want:
private void YourApp_LocationChanged(object sender, EventArgs e)
{
this.Opacity = 0.8;
}
You'll have to override WndProc and handle the exit move event to reset the opacity back to 1:
protected override void WndProc(ref Message m)
{
Trace.WriteLine(m.ToString());
switch (m.Msg)
{
case WMEXITSIZEMOVE:
this.Opacity = 1.0;
break;
}
base.WndProc(ref m);
}
Not forgetting to define the message code:
private const int WMEXITSIZEMOVE = 0x0232;
It might be more efficient to handle the WM_ENTERSIZEMOVE (code 0x0231) message instead of LocationChanged as this would only result in setting the opacity once (at the start of the drag) rather than continually throughout the drag.