MouseMove Observable on a ReactiveUserControl not getting all events - wpf

I'm trying to add an Observable to MouseMove events in a ReactiveUserControl using the following code at the constructor:
this.WhenActivated(disposables =>
{
var movingEvents = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(h => MouseMove += h, h => MouseMove -= h);
var mousePosition = movingEvents.Select(evt => evt.EventArgs.GetPosition(this));
mousePosition.Subscribe(pos => Console.WriteLine(pos.ToString()));
}
It works, but sometimes it simply stops "catching" the events for a random period. If I use the same code inside the MainWindow constructor, which is just a Window control, it appears to get all the events.
Is there anything I'm missing?

I would first consider using the ReactiveUI.Events.WPF NuGet package. It contains all the common UI events so you don't have to generate them yourself using FromEventPattern.
For the UserControl issue it's likely to be that you need the background set. WPF by default won't fire them off. See OnMouseMove does not fire on canvas in WPF for more details.

Related

Responsive UI in async drawing

I am trying to create a drawing with 5000 Shape objects in a background thread on a canvas.
I use the asyn-await pattern:
async void CreateDrawingAsync()
{
await Task.Run(() => CreateDrawing()).ConfigureAwait(false);
}
void CreateDrawing()
{
DrawObjects = new ObservableCollectionEx<DrawObject>();
// or: DrawObjects.Clear();
RaisePropertyChanged("DrawObjects");
// etc ... etc ...
}
ObservableCollectionEx means I use an extension of ObservableCollection to add an object to the collection via the Dispatcher.
When I start CreateDrawingAsync in the Loaded event of the Window (in the ctor of Data) the UI is unresponsive.
Using DispatcherPriority.Background the items are added one by one in the UI, but also in that case, the UI is unresponsive.
Loaded += (object sender, RoutedEventArgs e) =>
{
DataContext = new Data(2000, 1000, 1000);
};
1) I expected the background thread would resolve the unresponsive UI issue, what am I overlooking?
2) Why does RaisePropertyChanged("DrawObjects") (see code above) have no effect? I would have expected the drawing would be cleared due to the propertychanged.
You should not do UI operations on a background thread. This includes:
Drawing on a canvas.
Raising PropertyChanged notifications.
Creating or updating an ObservableCollection.
Creating a fake-UI component like ObservableCollectionEx that just forwards all its work to the UI thread doesn't gain you anything.

Setting Default Keyboard Focus On Loading A UserControl

I have an MVVM setup with a mainwindow that contains a ContentControl.
I set this to a particular viewmodel which then maps to a view.
A view is a usercontrol.
I want to be able to set the default keyboard focus to a default element in the usercontrol(View) when it loads so the application can eventually be driven just by using up, down, left, right and enter.
Some of my failed attempts are setting
FocusManager.FocusedElement="{Binding ElementName=DefaultElement}"
in my content control tag. This sets the logical focus but not the keyboard focus
I'd rather keep the solution in xaml if possable but have tried placing the following in code behind.
Keyboard.Focus(DefaultElement);
This does not work but if I popup a message box first it does. I'm a little confused as to why.
MessageBox.Show(Keyboard.FocusedElement.ToString());
Keyboard.Focus(DefaultElement);
EDIT::::
I just placed this in my onloaded event of my user control. It seems to work but can anyone see any issues that might arrise at this priority level. I.E a circumstance when the action will never run?
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
Keyboard.Focus(DefaultElement);
}));
It seems that this wpf the you have to implement a workaround on a case by case basis. The solution that seemed to work best, most of the time for me was to insert the focus code inside the dispatcher when OnVisible was changed. This sets the focus not only when the View/Usercontrol loads but also if you a changing Views by way of Visibility. If you Hide and then Show a ContentControl that is mapped to your ViewModels then the Loaded event won't fire and you'll be forced to Mouse input, or tabbing (Not so good if you want to navigate your app with a remote control).
VisibilityChanged will always fire however. This is what I ended up with for my listbox.
private void ItemsFlowListBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == true)
{
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
ItemsFlowListBox.Focus();
ItemsFlowListBox.ScrollIntoView(ItemsFlowListBox.SelectedItem);
}));
}
}
I had the same symptom for a WPF UserControl hosted in a Winforms application. Just wanted to note I was about to try this solution when I found a normal TabIndex in the Winforms app fixed it
Per How to set which control gets the focus on application start
"The one with the minimum tab index automatically gets the focus
(assuming the TabStop property is set to true). Just set the tab
indices appropriately."
It's a tricky one with no easy answer. I'm currently doing this, although I'm not sure I like it:
public MyView()
{
InitializeComponent();
// When DataContext changes hook the txtName.TextChanged event so we can give it initial focus
DataContextChanged +=
(sender, args) =>
{
txtName.TextChanged += OnTxtNameOnTextChanged;
};
}
private void OnTxtNameOnTextChanged(object o, TextChangedEventArgs eventArgs)
{
// Setting focus will select all text in the TextBox due to the global class handler on TextBox
txtName.Focus();
// Now unhook the event handler, since it's no longer required
txtName.TextChanged -= OnTxtNameOnTextChanged;
}
And in case you're wondering what the global class handler does, it's this:
protected override void OnStartup(StartupEventArgs e)
{
...
// Register a global handler for this app-domain to select all text in a textBox when
// the textBox receives keyboard focus.
EventManager.RegisterClassHandler(
typeof (TextBox), UIElement.GotKeyboardFocusEvent,
new RoutedEventHandler((sender, args) => ((TextBox) sender).SelectAll()));
which auto selects TextBox text when receiving keyboard focus.

How to prevent ScrollViewer from using MouseWheel event

I'm building SL application for zooming and panning across the layout. Everything is working fine, except that when I zoom in using mouse wheel , after some zoom scrollbars start to use mouse wheel so after that I can scroll not zoom. I only can zoom again if I put scrollbars at the end or begining. How to prevent scrollviewer from using mouse wheel? I want that zoom only be operated by wheel. Thank you in advance!
Here is my code of MouseWheel method when I'm zooming content :
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
if (e.Delta > 0)
{
this.aniScaleX.To += 0.2;
this.aniScaleY.To += 0.2;
this.sbScale.Begin();
}
else if (e.Delta < 0 && (this.aniScaleX.To > 1 && this.aniScaleY.To > 1))
{
this.aniScaleX.To -= 0.2;
this.aniScaleY.To -= 0.2;
this.sbScale.Begin();
}
Sizer.Width = Board.ActualWidth * (double)this.aniScaleX.To;
Sizer.Height = Board.ActualHeight * (double)this.aniScaleY.To;
Try to set:
e.Handled=true;
The MouseWheel event is a bubbling
event. This means that if multiple
MouseWheel event handlers are
registered for a sequence of objects
connected by parent-child
relationships in the object tree, the
event is potentially received by each
object in that relationship. The
bubbling metaphor indicates that the
event starts at the source and works
its way up the object tree. For a
bubbling event, the sender available
to the event handler identifies the
object where the event is handled, not
necessarily the object that actually
received the input condition that
initiated the event. To get the object
that initiated the event, use the
OriginalSource value of the event
data. http://msdn.microsoft.com/en-us/library/system.windows.uielement.mousewheel(VS.95).aspx
In my case,ScrollViewer always received event before because he is on the top of the visual tree. So I just registered event handler in scrollviewer on mouse wheel event and always when It happens, I simply redirect him to my "original" mousewheel function which do zoom.
I hope so that this will help somebody who is "stuck" like me here. Thank you all on your answers and suggestions..
This took me awhile (WPF), but basically you should use AddHandler in whatever parent UIElement that has the event.
For me, I did this in my MainWindow. So my code looked like:
public MainWindow()
{
InitializeComponent();
this.AddHandler(MainWindow.MouseWheelEvent, new RoutedEventHandler(this.MouseWheel_1), true);
}
This will also mean not overriding OnMouseWheelDown, but instead creating a method that matches the RoutedEventHandler delegate and casting e (which will be a RoutedEventArg) as MouseWheelEventArgs to get access to the properties required to determining zoom.
I hope this helps for your situation.

No events passed to WPF adorner layer

I am trying to make a nice "drag and drop zone" in WPF that is displayed in the adorner layer when something is being dragged into the main application. The problem is that I do not get any events from my adorner, even though it according to documentation should receive all input events since it is in a higher z-order.
To debug my problem I created a really simple example where I have a user control with only a button in it. This user control is displayed in the adorner layer, but I cannot click the button. Why? What have I done wrong?
My adorner class is constructed like this:
public ShellOverlayAdorner(UIElement element, AdornerLayer adornerLayer)
:base(element)
{
_adornerLayer = adornerLayer;
_overlayView = new AdornedElement();
_overlayView.AllowDrop = true;
_adornerLayer.Add(this);
}
and is created in the main window by
private void Window_Loaded(object sender, RoutedEventArgs e)
{
adornerLayer = AdornerLayer.GetAdornerLayer(MyTopGridWithButtonInIt);
ShellOverlayAdorner shell = new ShellOverlayAdorner(MyTopGridWithButtonInIt, adornerLayer);
}
I do not get any events at all from my control, i.e. no mouse clicks, mouse over, button clicks. I cannot even click the button in the adorner layer. What have I done wrong?
I don't know if you already tried that:
If you want the element added to react to events, I think that the element must be bound to the visual tree of the adorner.
The way to do it is to use a VisualCollection, intitialized to the adorner itself, or at least, this way it seems to be working:
VisualCollection visualChildren;
FrameworkElement #object;
public CustomAdorner(UIElement adornedElement) :
base(adornedElement)
{
visualChildren = new VisualCollection(this);
#object = new Button {Content = "prova"};
visualChildren.Add(#object);
}
protected override Visual GetVisualChild(int index)
{
return visualChildren[index];
}
This way the events are correctly routed.
I just had the same issue. Following the advice from MSDN sorted it for me:
Adorners receive input events just
like any other FrameworkElement.
Because an adorner always has a higher
z-order than the element it adorns,
the adorner receives input events
(such as Drop or MouseMove) that may
be intended for the underlying adorned
element. An adorner can listen for
certain input events and pass these on
to the underlying adorned element by
re-raising the event.
To enable pass-through hit testing of
elements under an adorner, set the hit
test IsHitTestVisible property to
false on the adorner.
i.e In the adorner itself, make sure IsHitTestVisible = false

How to show a loading graphic/animation when wpf data binding is taking place

I have a WPF user control that contains a DataGrid. I'm binding an ObservableCollection of view models to it. Each view model has another collection of view models that I'm using to bind another DataGrid to. So the effect is a DataGrid with a nested DataGrid contained in the row details template.
Normally the binding is quite quick, but sometimes when there's a lot of data it can hang the UI while the binding/drawing is taking place.
Is there a way where I can either show a loading animation or progress bar while the binding/drawing is in progress?
There's probably a more formal, or at least simpler solution, but you could use a modal popup window that is shown in a worker thread and is closed asynchronously when your is grid done loading:
Window waitWindow = new Window { Height = 100, Width = 200, WindowStartupLocation = WindowStartupLocation.CenterScreen, WindowStyle = WindowStyle.None };
waitWindow.Content = new TextBlock { Text = "Please Wait", FontSize = 30, FontWeight = FontWeights.Bold, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Dispatcher.BeginInvoke(new Action(delegate { waitWindow.ShowDialog(); }));
DataLoader dataLoader = new DataLoader(); // I made this class up
dataLoader.DataLoaded += delegate
{
Dispatcher.BeginInvoke(new Action(delegate() { waitWindow.Close(); }));
};
dataLoader.LoadData();
};
worker.RunWorkerAsync();
You can replace the TextBlock with something pretty like a loading bar, and you could make the code re-usable by parameterizing the object that handles the loading of the grid(s) and passing it in to a commonly used method.
I hope that works for you.
I had the same problem and this is how I solved it.
I discovered that DataGrid will only start creating controls when it displays the grid. In my case this was the time consuming process. After some tracing I found that creating the controls happens during measuring !
My solution is to override MeasureOverride and put the wait cursor around the base class call. I encapsulated my wait cursor setting in a class. So the code looks like this.
protected override Size MeasureOverride(Size availableSize)
{
using (new DisposableWaitCursor(this))
{
return base.MeasureOverride(availableSize);
}
}

Resources