If there is a way to send mouse click event by location programatically it would be great, but if theres another approach that can solve following problem this it is fine too.
In my situation I got a canvas taking up whole application size (covering it completely) and when user clicks it with mouse I want to hide it, and then pass through this mouse click (taking its location x & y from user) to anything that is under canvas (in my case canvas visibility goes to collapsed so controls under it can be seen now).
I am guessing it is impossible, cause certain features like run silverlight fullscreen can only be done in button click handler (correct me if im wrong here).
But is there a place where I can read about those security based limitations of silverlight UI ?
you have to add an click event handler to your canvas. In this handler you get the x and y positon of your click (via MouseButtonEventArgs) and then you can use the VisualTreeHelper to get your "hit elements".
Lets assume the following xaml:
<Grid x:Name="LayoutRoot" Background="White">
<Button Width="50" Height="50" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<TextBox Text="MyText" Width="200" Height="100" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Canvas Background="Red" x:Name="MyCanvas" />
</Grid>
with the following code behind:
public MainPage()
{
InitializeComponent();
MyCanvas.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(handler), true);
}
void handler(object sender, MouseButtonEventArgs e)
{
var point = new Point(e.GetPosition(this).X, e.GetPosition(this).Y);
var elements = VisualTreeHelper.FindElementsInHostCoordinates(point, this);
foreach (var uiElement in elements)
{
if (uiElement is TextBox){
((TextBox) uiElement).Focus();
break;
}
if(uiElement is Button)
{
//do button stuff here
break;
}
}
MyCanvas.Visibility = Visibility.Collapsed;
MyCanvas.RemoveHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(handler));
}
But: In this simple example, you get at about 20 hit elements. But they are sorted in the correct "z-Index". So you can iterate through it and the first interesting element for you is where you could break(Maybe you can do this with LINQ, too). So for me, I know that the first hit TextBox is what I want to focus.
Is this what you need?
BR,
TJ
Related
I'm having some issues trying to figure out how to scroll the content of a grid which is contained inside of a scroll viewer. When trying to scroll with the mouse wheel or pan (with a touch screen), the grid scrolls fine if the mouse/touch point is over an empty area, but if it is above certain controls (ex. a group box) it won't scroll. Is there some property I'm missing to allow the child panels to allow them to scroll their parent containers?
EDIT:
I incorrectly stated my original layout. Here's a simplified version of my senario:
<Grid>
<ScrollViewer Name="MainScrollViewer">
<StackPanel>
<ListBox /> <--Doesn't Scroll-->
<Button /> <--Scrolls Fine-->
<TextBlock /> <--Scrolls Fine-->
<TextBox /> <--Scrolls Fine-->
<DataGrid /> <--Doesn't Scroll-->
</StackPanel>
</ScrollViewer>
</Grid>
A coworker pointed out that my issue is due to the fact the controls such as a ListBoxes and DataGrids contain ScrollViewers themselves, this makes sense. His suggestion (which would work but we both agree seems more complex than it should be) is to catch and rethrow the the scroll event in the code behind (and likely have to deal with calculating the smount of offset to scroll) so that it can bubble up to "MainScrollViewer".
EDIT 2:
It seems like the only way to achieve this is to use code behind to handle the PreviewMouseWheel event in the parent. That works, but how do I go about implementing the same thing for panning (scrolling by finger on a touch screen)?
Create a bubbling scrollbehavior for your scrollview:
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
public sealed class BubbleScrollEvent : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
base.OnDetaching();
}
void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (!e.Handled)
{
e.Handled = true;
var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent };
AssociatedObject.RaiseEvent(e2);
}
}
}
Add this behavior to your scrollviewer:
<ScrollViewer x:Name="Scroller">
<i:Interaction.Behaviors>
<ViewAddons:BubbleScrollEvent />
</i:Interaction.Behaviors>
Use ScrollViewer's PreviewMouseWheel event and ScrollToVerticalOffset method...
private void ScrollViewerOnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scv = sender as ScrollViewer;
if (scv == null) return;
scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
e.Handled = true;
}
For Starters, be sure you are using the Tech that already exists. That may resolve your issue.
<Grid HorizontalAlignment="Left" Height="300" Margin="10,10,0,0" VerticalAlignment="Top" Width="497" ScrollViewer.VerticalScrollBarVisibility="Visible" />
Although if that doesn't resolve the issue, and i know this sounds strange, Set the background color of the grid AND problem object to #00000000. (if it is not already assigned a color/brush)
<Grid HorizontalAlignment="Left" Height="300" Margin="10,10,0,0" VerticalAlignment="Top" Width="497" Background="#00000000"/>
I know its strange, but when I have these problems it works every time. I have no idea why it works. Something to do with transparency.
Apparently, there are some serious keyboard and focus issues with WPF WebBrowser control. I've put together a trivial WPF app, just a WebBrowser and two buttons. The app loads a very basic editable HTML markup (<body contentEditable='true'>some text</body>) and demonstrates the following:
Tabbing is misbehaving. User needs to hit Tab twice to see the caret (text cursor) inside WebBrowser and be able to type.
When user switches away from the app (e.g., with Alt-Tab), then goes back, the caret is gone and she is unable to type at all. A physical mouse click into the WebBrowser's window client area is required to get back the caret and keystrokes.
Inconsistently, a dotted focus rectangle shows up around WebBrowser (when tabbing, but not when clicking). I could not find a way to get rid of it (FocusVisualStyle="{x:Null}" does not help).
Internally, WebBrowser never receives the focus. That's true for both logical focus (FocusManager) and input focus (Keyboard). The Keyboard.GotKeyboardFocusEvent and FocusManager.GotFocusEvent events never get fired for WebBrowser (although they both do for buttons in the same focus scope). Even when the caret is inside WebBrowser, FocusManager.GetFocusedElement(mainWindow) points to a previously focused element (a button) and Keyboard.FocusedElement is null. At the same time, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() returns true.
I'd say, such behaviour is almost too dysfunctional to be true, but that's how it works. I could probably come up with some hacks to fix it and bring it in row with native WPF controls like TextBox. Still I hope, maybe I'm missing something obscure yet simple here. Has anyone dealt with a similar problem? Any suggestions on how to fix this would be greatly appreciated.
At this point, I'm inclined to develop an in-house WPF wrapper for WebBrowser ActiveX Control, based upon HwndHost. We are also considering other alternatives to WebBrowser, such as Chromium Embedded Framework (CEF).
The VS2012 project can be downloaded from here in case someone wants to play with it.
This is XAML:
<Window x:Class="WpfWebBrowserTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="640" Height="480" Background="LightGray">
<StackPanel Margin="20,20,20,20">
<ToggleButton Name="btnLoad" Focusable="True" IsTabStop="True" Content="Load" Click="btnLoad_Click" Width="100"/>
<WebBrowser Name="webBrowser" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="300"/>
<Button Name="btnClose" Focusable="True" IsTabStop="True" Content="Close" Click="btnClose_Click" Width="100"/>
</StackPanel>
</Window>
This is C# code, it has a bunch of diagnostic traces to show how focus/keyboard events are routed and where the focus is:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
namespace WpfWebBrowserTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// watch these events for diagnostics
EventManager.RegisterClassHandler(typeof(UIElement), Keyboard.PreviewKeyDownEvent, new KeyEventHandler(MainWindow_PreviewKeyDown));
EventManager.RegisterClassHandler(typeof(UIElement), Keyboard.GotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(MainWindow_GotKeyboardFocus));
EventManager.RegisterClassHandler(typeof(UIElement), FocusManager.GotFocusEvent, new RoutedEventHandler(MainWindow_GotFocus));
}
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
// load the browser
this.webBrowser.NavigateToString("<body contentEditable='true' onload='focus()'>Line 1<br>Line 3<br>Line 3<br></body>");
this.btnLoad.IsChecked = true;
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
// close the form
if (MessageBox.Show("Close it?", this.Title, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
this.Close();
}
// Diagnostic events
void MainWindow_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
Debug.Print("{0}, source: {1}, {2}", FormatMethodName(), FormatType(e.Source), FormatFocused());
}
void MainWindow_GotFocus(object sender, RoutedEventArgs e)
{
Debug.Print("{0}, source: {1}, {2}", FormatMethodName(), FormatType(e.Source), FormatFocused());
}
void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
Debug.Print("{0}, key: {1}, source: {2}, {3}", FormatMethodName(), e.Key.ToString(), FormatType(e.Source), FormatFocused());
}
// Debug output formatting helpers
string FormatFocused()
{
// show current focus and keyboard focus
return String.Format("Focus: {0}, Keyboard focus: {1}, webBrowser.HasFocusWithin: {2}",
FormatType(FocusManager.GetFocusedElement(this)),
FormatType(Keyboard.FocusedElement),
((System.Windows.Interop.IKeyboardInputSink)this.webBrowser).HasFocusWithin());
}
string FormatType(object p)
{
string result = p != null ? String.Concat('*', p.GetType().Name, '*') : "null";
if (p == this.webBrowser )
result += "!!";
return result;
}
static string FormatMethodName()
{
return new StackTrace(true).GetFrame(1).GetMethod().Name;
}
}
}
[UPDATE] The situation doesn't get better if I host WinForms WebBrowser (in place of, or side-by-side with WPF WebBrowser):
<StackPanel Margin="20,20,20,20">
<ToggleButton Name="btnLoad" Focusable="True" IsTabStop="True" Content="Load" Click="btnLoad_Click" Width="100"/>
<WebBrowser Name="webBrowser" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="150" Margin="10,10,10,10"/>
<WindowsFormsHost Name="wfHost" Focusable="True" Height="150" Margin="10,10,10,10">
<wf:WebBrowser x:Name="wfWebBrowser" />
</WindowsFormsHost>
<Button Name="btnClose" Focusable="True" IsTabStop="True" Content="Close" Click="btnClose_Click" Width="100"/>
</StackPanel>
The only improvement is that I do see focus events on WindowsFormsHost.
[UPDATE] An extreme case: two WebBrowser controls with two carets showing at the same time:
<StackPanel Margin="20,20,20,20">
<ToggleButton Name="btnLoad" Focusable="True" IsTabStop="True" Content="Load" Click="btnLoad_Click" Width="100"/>
<WebBrowser Name="webBrowser" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="150" Margin="10,10,10,10"/>
<WebBrowser Name="webBrowser2" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="150" Margin="10,10,10,10"/>
<Button Name="btnClose" Focusable="True" IsTabStop="True" Content="Close" Click="btnClose_Click" Width="100"/>
</StackPanel>
this.webBrowser.NavigateToString("<body onload='text.focus()'><textarea id='text' style='width: 100%; height: 100%'>text</textarea></body>");
this.webBrowser2.NavigateToString("<body onload='text.focus()'><textarea id='text' style='width: 100%; height: 100%'>text2</textarea></body>");
This also illustrates that the focus handling issue is not specific to contentEditable=true content.
For anyone else stumbling upon this post and needing to set keyboard focus to the browser control (not a particular element within the control, necessarily), this bit of code worked for me.
First, add a project reference (under Extensions in VS) for Microsoft.mshtml.
Next, whenever you'd like to focus the browser control (say for example, when the Window loads), simply "focus" the HTML document:
// Constructor
public MyWindow()
{
Loaded += (_, __) =>
{
((HTMLDocument) Browser.Document).focus();
};
}
This will place keyboard focus inside the web browser control, and inside the "invisible" ActiveX window, allowing keys like PgUp / PgDown to work on the HTML page.
If you want to, you might be able to use DOM selection to find a particular element on the page, and try to focus() that particular element. I have not tried this myself.
The reason it behaves this way is related to the fact that it's an ActiveX control which itself is a fully windows class (it handles mouse and keyboard interaction). In fact much of the time you see the component used you'll find it is the main component taking up a full window because of this. It doesn't have to be done that way but it presents issues.
Here's a forum discussing the exact same issue and it's causes can be clarified by reading the last commentators article links:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/1b50fec6-6596-4c0a-9191-32cd059f18f7/focus-issues-with-systemwindowscontrolswebbrowser
To outline the issues you're having
Tabbing is misbehaving. User needs to hit Tab twice to see the caret (text cursor) inside WebBrowser and be able to type.
that's because the browser control itself is a window which can be tabbed to. It doesn't "forward" the tab to it's child elements immediately.
One way to change this would be to handle the WM message for the component itself but keep in mind that doing so gets tricky when you want the "child" document inside of it to be able to handle messages.
See: Prevent WebBrowser control from stealing focus? specifically the "answer". Although their answer doesn't account that you can control whether the component interacts through dialogs with the user by setting the Silent property (may or may not exist in the WPF control... not sure)
When user switches away from the app (e.g., with Alt-Tab), then goes back, the caret is gone and she is unable to type at all. A physical mouse click into the WebBrowser's window client area is required to get back the caret and keystrokes.
This is because the control itself has received the focus. Another consideration is to add code to handle the GotFocus event and to then "change" where the focus goes. Tricky part is figuring out if this was "from" the document -> browser control or your app -> browser control. I can think of a few hacky ways to do this (variable reference based on losing focus event checked on gotfocus for example) but nothing that screams elegant.
Inconsistently, a dotted focus rectangle shows up around WebBrowser (when tabbing, but not when clicking). I could not find a way to get rid of it (FocusVisualStyle="{x:Null}" does not help).
I wonder if changing Focusable would help or hinder. Never tried it but I'm going to venture a guess that if it did work it would stop it from being keyboard navigable at all.
Internally, WebBrowser never receives the focus. That's true for both logical focus (FocusManager) and input focus (Keyboard). The Keyboard.GotKeyboardFocusEvent and FocusManager.GotFocusEvent events never get fired for WebBrowser (although they both do for buttons in the same focus scope). Even when the caret is inside WebBrowser, FocusManager.GetFocusedElement(mainWindow) points to a previously focused element (a button) and Keyboard.FocusedElement is null. At the same time, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() returns true.
People have hit issues with where 2 browser controls both show the focus(well... the caret) or even had a hidden control take the focus.
All in all it's pretty awesome what you can do with the component but it's just the right mix of letting you control/change the behavior along with predefined sets of behavior to be maddening.
My suggestion would be to try to subclass the messages so you can direct the focus control directly through code and bypass it's window from trying to do so.
How to force the layout measurements update?
I have simplified layout I am problem with; when you click the button first time you get one measurement and on the second click different one.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var w = mywindow.ActualWidth;
gridx.Width = w;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
btn3.Width = 100;
var w = mywindow.ActualWidth;
gridx.Width = w - btn3.Width;
InvalidateArrange();
InvalidateMeasure();
MessageBox.Show(btn1.ActualWidth.ToString());
}
Window
<Window x:Class="resizet.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" Loaded="Window_Loaded" Name="mywindow">
<DockPanel HorizontalAlignment="Stretch" LastChildFill="False">
<Grid HorizontalAlignment="Stretch" DockPanel.Dock="Left" Name="gridx">
<Button HorizontalAlignment="Stretch" Content="btn in grid" Click="Button_Click" />
</Grid>
<Button Name="btn2" Content="btn2" Width="0" DockPanel.Dock="Right" HorizontalAlignment="Left"></Button>
</DockPanel>
</Window>
This fixes the problem:
btn3.Width = 100;
btn3.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
var w = mywindow.ActualWidth;
gridx.Width = w - btn3.Width;
with additional
private static Action EmptyDelegate = delegate() { };
Changing the Width property must invalidate the layout on its own, you don't need to call InvalidateXXX() yourself.
The catch is that the layout is not updated immediately, but on the next iteration of the message loop. So the ActualWidth will not be changed immediately.
If you want the Grid to resize automatically when the button width is increasing, why not use the layout management and put the both into different columns of an outer Grid?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid x:Name="gridx"
Grid.Column="0">
<Button HorizontalAlignment="Stretch"
Click="Button_Click"/>
</Grid>
<Button x:Name="btn2"
Content="btn2"
Width="0"
Grid.Column="1"/>
</Grid>
And in code-behind
private void Button_Click(object sender, RoutedEventArgs e)
{
btn2.Width = 100;
}
In a strict sense, #Daniel has provided some code that fixes the problem posed by the question. But the result is rather bad, putting layouting code into an event handler. The grid and the button might look good after the button got pressed, but once the user makes the window size bigger, the grid will not grow and will not use the available size. The user would have to press the button again to make the grid grow. That's most likely not how things should be and that's why #Vlad's answer is better.
WPF uses just one thread to process events and layouting, but they get executed in different phases. If width gets changed, the MeasureDirty flag of the control gets set, then the processing of the event continues immediately. Once this event and all other events needing processing are completed, only then starts WPF with the layouting (i.e. measure, arrange, render). Here is an overview how that works:
For a detailed description what happens in every step, see my article on CodeProject Deep Dive into WPF Layouting and Rendering
btn3.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
This statement halts the execution of the button event handler and forces a whole layouting / render phase to run, only then continues with the event handler code to change the width of the grid, which will force another layouting / render cycle to run.
Recommendations:
Do not set Height and Width in event handlers, unless you want them to be fixed and not to change, even the available space changes.
Use WPF controls like Grid, etc. to make best use of the available space
If you cannot find a WPF control like Grid which matches your layouting needs, write your own Control and put all the layouting code into MeasureOverride() and ArrangeOverride().
I'm creating a WPF application containing a "Main-Content" - Layer containing a TabControl and a "Dialog" - Layer containing an ItemsControl.
The XAML looks like this:
<Grid>
<TabControl>
..Some Tabs
</TabControl>
<ItemsControl>
<ContentControl Content={Binding Dialog1Property} />
<ContentControl Content={Binding Dialog2Property} />
</ItemsControl>
</Grid>
Usually "Dialog1Property" and "Dialog2Property" are null which means the ItemsControl is invisible. Whenever I assign a Control to one of them, it is shown in front of the TabControl which is exactly what I want. If I assign a gray Rectangle with an opacity of 0.7 to one of the Dialog - Properties it creates a Gray overlay.
If I click on the Tab, which is slightly visible through the overlay, nothing happens - the Rectangle blocks Mouse Events. It is, however, still possible to focus the TabControl behind the overlay using the Tab-Key and therefore it is also possible to switch tabs even though a Dialog is shown.
Is there an easy way to tell the rectangle to somehow block key events as it allready does with Mouseclicks?
Regards
BBRain
Yes, on your Rectangle, subscribe to the event PreviewKeyDown.
<Rectangle Opacity="0.7" Fill="Green" PreviewKeyDown="Rectangle_PreviewKeyDown" />
In its handler, simply set e.Handled = true;
private void Rectangle_PreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
}
Since routed events prefixed with "Preview..." are tunneling, the elements under your rectangle won't recieve the input.
I'm trying to implement a piece of functionality that will let the user to drag files into an application to be opened in the FlowDocumentReader.
My problem is that is though I have AllowDrop=true on the FlowDocumentReader, the cursor does not change to the "drop here" icon but changes instead to "drop is not allowed" icon.
This happens only to the FlowDocumentReader, all other parts og the UI (window itself, other controls) work as expected. The FlowDocumentReader actually receives the events, and it is possible to handle the drop, but the user does not have a visual indication that he can release the mouse here.
I also cannot hide the "drop is not allowed" cursor by setting Cursor=Cursors.None
Need to handle DragOver event in FlowDocument to allow dropping here.
xaml:
<!--
<FlowDocumentReader x:Name="fdr" Background="White">
<FlowDocument x:Name="doc" AllowDrop="True" DragEnter="doc_DragOver" Drop="doc_Drop" Background="White"/>
</FlowDocumentReader>
-->
<FlowDocumentReader x:Name="fdr" Background="White">
<FlowDocument x:Name="doc" AllowDrop="True" DragOver="doc_DragOver" Drop="doc_Drop" Background="White"/>
</FlowDocumentReader>
code behind:
private void doc_DragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.All;
e.Handled = true;
}
private void doc_Drop(object sender, DragEventArgs e)
{
}
I couldn't find any direct way to solve this, so here is what I have ended up with:
I placed a grid on top of the FlowDocumentReader. This grid has a sold color, opacity of 0 (transparent) and Visibility=Collapsed. The purpose of this grid is to serve as a drop target.
When FlowDocument within the FlowDocumentReader received the DragEnter event, I switch the grid's visibility to Visible. The grid starts receiving drag events and the cursor stays in the "drop here" form.
When grid receives Drop or DragLeave events, its visibility is turned back to Collapsed to allow the FlowDocument receive mouse events
<FlowDocumentReader x:Name="fdr" Grid.Row="1" Background="White">
<FlowDocument x:Name="doc" DragEnter="doc_DragEnter" Background="White"/>
</FlowDocumentReader>
<Grid x:Name="dtg" Grid.Row="1" Background="White" Opacity="0"
Drop="dtg_Drop" DragLeave="dtg_DragLeave" Visibility="Collapsed"/>