manipulate selected text on wpf webbrowser - wpf

I'm creating a epub books reader.
After displaying the book i want to allow users to add some annotation to the book.
To display the book, I'm using a wpf webbrowser control that loads local html files
I want to manipulate selected text on this control by creating a context menu or showing a popup
i've tried to change the control's contextmenu but by searching i found that isn't possible
this is an example of what i want to do with selected text:
IHTMLDocument2 htmlDocument = (IHTMLDocument2)webBrowser1.Document;
IHTMLSelectionObject currentSelection = htmlDocument.selection;
if (currentSelection != null)
{
IHTMLTxtRange range = currentSelection.createRange() as IHTMLTxtRange;
if (range != null)
{
MessageBox.Show(range.text);
}
}

WPF's native browser control will not let you set a custom context menu.
It gets even worse ; while your mouse is over the browser component, or if it has focus, it will not catch events generated by your input either.
A way around this, is to use the windows forms browser control inside a WindowsFormsHost.
To start, add Windows.Forms to your project references.
Then, do something like the following:
XAML:
<Window x:Class="blarb.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>
<WindowsFormsHost Name="windowsFormsHost" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</Grid>
</Window>
C# code:
public partial class MainWindow : Window
{
private System.Windows.Forms.WebBrowser Browser;
public MainWindow()
{
InitializeComponent();
//initialise the windows.forms browser component
Browser = new System.Windows.Forms.WebBrowser
{
//disable the default context menu
IsWebBrowserContextMenuEnabled = false
};
//make a custom context menu with items
System.Windows.Forms.ContextMenu BrowserContextMenu = new System.Windows.Forms.ContextMenu();
System.Windows.Forms.MenuItem MenuItem = new System.Windows.Forms.MenuItem {Text = "Take Action"};
MenuItem.Click += MenuItemOnClick;
BrowserContextMenu.MenuItems.Add(MenuItem);
Browser.ContextMenu = BrowserContextMenu;
//put the browser control in the windows forms host
windowsFormsHost.Child = Browser;
//navigate the browser like this:
Browser.Navigate("http://www.google.com");
}
private void MenuItemOnClick(object sender, EventArgs eventArgs)
{
//will be called when you click the context menu item
}
}
This does not yet explain how to do your highlighting though.
You could listen for the event fired by the browser component when it is done loading, and then replace portions of the document it loaded, injecting html code to do the highlighting.
Keep in mind that that might be tricky in some situations (when selecting text across divs, spans or paragraphs for example)

using mshtml;
private mshtml.HTMLDocumentEvents2_Event documentEvents;
in constructor or xaml set your LoadComplete event:
webBrowser.LoadCompleted += webBrowser_LoadCompleted;
then in that method create your new webbrowser document object and view the available properties and create new events as follows:
private void webBrowser_LoadCompleted(object sender, NavigationEventArgs e)
{
documentEvents = (HTMLDocumentEvents2_Event)webBrowserChat.Document; // this will access the events properties as needed
documentEvents.oncontextmenu += webBrowserChat_ContextMenuOpening;
}
private bool webBrowserChat_ContextMenuOpening(IHTMLEventObj pEvtObj)
{
return false; // ContextMenu wont open
// return true; ContextMenu will open
// Here you can create your custom contextmenu or whatever you want
}

Related

Custom Usercontrol with MVVM and Catel

I've created a custom usercontrol that's composed of a AutoCompleteBox with a Selected Item... till now I've implemented it in a way I don't like... I mean I've a XAML view, a Viewmodel and in the viewmodel I load data from a stored procedure.
Since the AutoComplete box is a third party UserControl I've added it to the XAML view and not defined as a custom usercontrol. What's the best practice to do so?
I think the fact that I'm using Catel as MVVM Framework is irrilevant right now..
Thanks
UPDATE #1
My usercontrols need to have some properties that are passed via XAML for example (LoadDefaultValue)
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding Model.PortfolioModel}" Height="25" LoadDefaultValue="True" Width="150" />
To achieve such a scenario I had to define a dependency property in my PortfolioChooserView defined as
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
Since if I would have defined it in Viewmodel only I wouldn't have been able to set it.
The odd thing is that in order to pass it to the viewmodel I had to do such a trick
public PortfolioChooserView()
{
InitializeComponent();
if (!isFirstLoad) return;
Focusable = true;
PortfolioCompleteBox.AllowDrop = true;
PortfolioCompleteBox.Focus();
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
DataContextChanged += PortfolioChooserView_DataContextChanged;
isFirstLoad = false;
}
void PortfolioChooserView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataContext = DataContext as PortfolioModel;
if (dataContext != null)
{
dataContext.LoadDefaultValue = LoadDefaultValue;
dataContext.AllowNull = AllowNull;
//var converter = new PortfolioConverter();
//var portfolio = (Portfolio) converter.Convert(SelectedItem, null, null, CultureInfo.CurrentCulture);
//dataContext.SelectedItem = portfolio;
}
}
But I really dislike to use the DataContextChanged event ...do you see a better approach?
Thank
UPDATE#2
I keep this toghether since It's a related question...
On some viewmodel I used DeferValidationUntilFirstSaveCall = true; in the Constructor to disable the validation at load but my custom usercontrols shows the red border around... what should I do to propagate that info to the nested usercontrols?
Thanks again
See Orc.Controls for tons of examples. It's an open-source library that has a lot of user controls built with Catel, even one with an auto complete box.

How do I get access to a MessageBox through WPF Automation API?

How do I get access to MessageBox using the low level WPF Automation API?
I have searched all over but there seems to be very little documentation for this. I would rather not use White as I need more control than it gives.
Thanks
Lets suppose you have that simple WPF application:
Xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Button Name="Button1" Content="Click Me" Click="Button1_Click" />
</Grid>
</Window>
Code:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "hello");
}
}
You can automate this application with a console app sample like this (run this once you have started the first project):
class Program
{
static void Main(string[] args)
{
// get the WPF app's process (must be named "WpfApplication1")
Process process = Process.GetProcessesByName("WpfApplication1")[0];
// get main window
AutomationElement mainWindow = AutomationElement.FromHandle(process.MainWindowHandle);
// get first button (WPF's "Button1")
AutomationElement button = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
// click it
InvokePattern invoke = (InvokePattern)button.GetCurrentPattern(InvokePattern.Pattern);
invoke.Invoke();
// get the first dialog (in this case the message box that has been opened by the previous button invoke)
AutomationElement dlg = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "Dialog"));
AutomationElement dlgText = dlg.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Text));
Console.WriteLine("Message Box text:" + dlgText.Current.Name);
// get the dialog's first button (in this case, 'OK')
AutomationElement dlgButton = dlg.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
// click it
invoke = (InvokePattern)dlgButton.GetCurrentPattern(InvokePattern.Pattern);
invoke.Invoke();
}

Open an xaml file inside a popup window in silverlight

I need to open an xaml file inside a popup window in silverlight.
In my case i am having two xaml files namely a.axml and b.axml.,
In a.xaml, there is a hyper link button and a popup tag inside it.
I have to open the b.xaml page inside that popup window on clicking the hyperlink of the a.xaml page.
This is my scenario. Pls help me to solve this issue.
thanks,
Neon
You should be able to set the Child property of the popup to be a new instance of your b.xaml class (whatever that is).
Using Telerik controls its getting easier. But you may use second method.
On Blend I changed the template of Telerik's child window. It seems better now
HyperlinkButton link = new HyperlinkButton();
link.Click += (s, a) =>
{
//With telerik
RadWindow w = new RadWindow();
w.Content=new UserControl();
w.Show();
w.ShowDialog();//Swith light and avoid access oter controls
//Without Telerik
Popup p = new Popup();
p.Child = w.Content as UserControl;
p.IsOpen = true;
};
Parent Page XAML:
<HyperlinkButton Content="HyperlinkButton" Height="23" HorizontalAlignment="Left" Margin="150,111,0,0" Name="hyperlinkButton1" VerticalAlignment="Top" Width="100" Click="hyperlinkButton1_Click" />
Code Behind :
public ParentPage()
{
InitializeComponent();
}
private void hyperlinkButton1_Click(object sender, RoutedEventArgs e)
{
ChildPage objpage = new ChildPage();
objpage.Show();
}
Here Is Pararent Page have buuton . child page load based on That button Click

Dockable Windows. Floating Window and MainWindow Menu Integration

In Visual Studio 2010, Dockable Windows seem to work like expected in every situation.
If a "Floating" document is active and some menu is selected (e.g Edit -> Paste), then the "Floating" document still has Focus and the command will be executed against that "Floating" window. Also, notice how this is clearly visible in the UI. MainWindow.xaml is still active and the Main window in Visual Studio is inactive even though the Team-menu is selected.
I've been trying to get the same behavior using alot of different 3rd-party docking components but they all have the same problem: once I select the menu, the MainWindow is focused and my floating window does not have focus anymore. Does anyone know of a way to get the same behavior here as in Visual Studio?
At the moment I'm using Infragistics xamDockManager and the problem can be reproduced with the following sample code.
Right click "Header 1" and select "Float"
Click the "File" menu
Notice how MainWindow receives focus.
xmlns:igDock="http://infragistics.com/DockManager"
<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New"/>
</MenuItem>
</Menu>
<Grid>
<igDock:XamDockManager x:Name="dockManager" Theme="Aero">
<igDock:DocumentContentHost>
<igDock:SplitPane>
<igDock:TabGroupPane>
<igDock:ContentPane Header="Header 1">
<TextBox Text="Some Text"/>
</igDock:ContentPane>
<igDock:ContentPane Header="Header 2">
<TextBox Text="Some Other Text"/>
</igDock:ContentPane>
</igDock:TabGroupPane>
</igDock:SplitPane>
</igDock:DocumentContentHost>
</igDock:XamDockManager>
</Grid>
</DockPanel>
The visual studio team has some good information on lessons they learned when making VS in WPF. One of the issues they ran into was related to Focus management. As a result, WPF 4 has some new features to help out.
Here's the info on the issue that sounds like your situation:
http://blogs.msdn.com/b/visualstudio/archive/2010/03/09/wpf-in-visual-studio-2010-part-3-focus-and-activation.aspx
Their discussion of the new "HwndSource.DefaultAcquireHwndFocusInMenuMode" property sounds very similar to what you're running into.
EDIT
After further investigation, it looks like Visual Studio might be hooking the windows message loop and returning specific values to make the floating windows work.
I'm not a win32 programmer, but it seems that when a user clicks a menu in an inactive window, windows sends the WM_MOUSEACTIVATE message to it before processing the mouse down event. This lets the main window determine whether it should be activated.
In my unmodified WPF test app, the inactive window returns MA_ACTIVATE. However, VS returns MA_NOACTIVATE. The docs indicate that this tells windows NOT to activate the main window prior to handling further input. I'm guessing that visual studio hooks the windows message loop and returns MA_NOACTIVATE when the user clicks on the menus / toolbars.
I was able to make this work in a simple, two window WPF app by adding this code to the top level window.
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var hook = new HwndSourceHook(this.FilterMessage);
var source2 = HwndSource.FromVisual(this) as HwndSource;
source2.AddHook(hook);
}
private IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_MOUSEACTIVATE = 0x0021;
const int MA_NOACTIVATE = 3;
switch (msg)
{
case WM_MOUSEACTIVATE:
handled = true;
return new IntPtr(MA_NOACTIVATE);
}
return IntPtr.Zero;
}
In your case, you'd probably need to add more logic that would check what the user clicked on and decide based on that whether to intercept the message and return MA_NOACTIVATE.
EDIT 2
I've attached a sample WPF application that shows how to do this with a simple WPF application. This should work pretty much the same with floating windows from a docking toolkit, but I haven't tested that specific scenario.
The sample is available at: http://blog.alner.net/downloads/floatingWindowTest.zip
The sample has code comments to explain how it works. To see it in action, run the sample, click the "open another window" button. This should put focus in the textbox of the new window. Now, click the edit menu of the main window and use the commands like "select all". These should operate on the other window without bringing the "main window" to the foreground.
You can also click on the "exit" menu item to see that it can still route commands to the main window if needed.
Key Points (Activation / Focus):
Use the HwndSource.DefaultAcquireHwndFocusInMenuMode to get the menus to work stop grabbing focus.
Hook the message loop and return "MA_NOACTIVATE" when the user clicks the menu.
Add an event handler to the menu's PreviewGotKeyboardFocus and set e.Handled to true so that the menu wont' attempt to grab focus.
Key Points (Commands):
Hook the main window's "CommandManager.PreviewCanExecute" and "CommandManager.PreviewExecuted" events.
In these events, detect whether the app has an "other window" that's supposed to be the target of events.
Manually invoke the original command against the "other window".
Hope it works for you. If not, let me know.
I used the great answer from NathanAW and created a ResourceDictionary containing a Style for Window (which should be used by the MainWindow), contained the key pieces to solve this problem.
Update: Added support for ToolBar as well as Menu
It includes hit testing specifically for the MainMenu or ToolBar to decide if focusing should be allowed.
The reason I've used a ResourceDictionary for this is for reusability since we will be using this in many projects. Also, the code behind for the MainWindow can stay clean.
MainWindow can use this style with
<Window...>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NoFocusMenuWindowDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Window.Style>
<StaticResource ResourceKey="NoFocusMenuWindow"/>
</Window.Style>
<!--...-->
</Window>
NoFocusMenuWindowDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MainWindowVS2010Mode.NoFocusMenuWindowDictionary">
<Style x:Key="NoFocusMenuWindow" TargetType="Window">
<EventSetter Event="Loaded" Handler="MainWindow_Loaded"/>
</Style>
<Style TargetType="Menu">
<EventSetter Event="PreviewGotKeyboardFocus"
Handler="Menu_PreviewGotKeyboardFocus"/>
</Style>
<Style TargetType="ToolBar">
<EventSetter Event="PreviewGotKeyboardFocus"
Handler="ToolBar_PreviewGotKeyboardFocus"/>
</Style>
</ResourceDictionary>
NoFocusMenuWindowDictionary.xaml.cs
namespace MainWindowVS2010Mode
{
public partial class NoFocusMenuWindowDictionary
{
#region Declaration
private static Window _mainWindow;
private static bool _mainMenuOrToolBarClicked;
#endregion // Declaration
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_mainWindow = sender as Window;
HwndSource.DefaultAcquireHwndFocusInMenuMode = true;
Keyboard.DefaultRestoreFocusMode = RestoreFocusMode.None;
HwndSource hwndSource = HwndSource.FromVisual(_mainWindow) as HwndSource;
hwndSource.AddHook(FilterMessage);
}
private static IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_MOUSEACTIVATE = 0x0021;
const int MA_NOACTIVATE = 3;
switch (msg)
{
case WM_MOUSEACTIVATE:
if (ClickedMainMenuOrToolBarItem())
{
handled = true;
return new IntPtr(MA_NOACTIVATE);
}
break;
}
return IntPtr.Zero;
}
#region Hit Testing
private static bool ClickedMainMenuOrToolBarItem()
{
_mainMenuOrToolBarClicked = false;
Point clickedPoint = Mouse.GetPosition(_mainWindow);
VisualTreeHelper.HitTest(_mainWindow,
null,
new HitTestResultCallback(HitTestCallback),
new PointHitTestParameters(clickedPoint));
return _mainMenuOrToolBarClicked;
}
private static HitTestResultBehavior HitTestCallback(HitTestResult result)
{
DependencyObject visualHit = result.VisualHit;
Menu parentMenu = GetVisualParent<Menu>(visualHit);
if (parentMenu != null && parentMenu.IsMainMenu == true)
{
_mainMenuOrToolBarClicked = true;
return HitTestResultBehavior.Stop;
}
ToolBar parentToolBar = GetVisualParent<ToolBar>(visualHit);
if (parentToolBar != null)
{
_mainMenuOrToolBarClicked = true;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}
public static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
#endregion // Hit Testing
#region Menu
private void Menu_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
Menu menu = sender as Menu;
if (menu.IsMainMenu == true)
{
e.Handled = true;
}
}
#endregion // Menu
#region ToolBar
private void ToolBar_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
#endregion // ToolBar
}
}
Just out of curiosity, have you tried binding the MenuItem.CommandTarget to the XamDockManager.ActivePane?
Looking at the XamDockManager documentation, I also see a CurrentFlyoutPane property which returns "the Infragistics.Windows.DockManager.ContentPane currently within the UnpinnedTabFlyout or null if the flyout is not shown." I'm not sure which property would be appropriate in your scenario, but it's worth a try.
I know this is an old post, but Prism could make your life so much easier. Using the RegionAdapter created here:
http://brianlagunas.com/2012/09/12/xamdockmanagera-prism-regionadapter/
You can easily track which window is active, floating or not, by using the IActiveAware interface. Prisms commands also take this into consideration and can excute commands only on the active view. The blog post has a sample app you can play around with.
Im not sure about how to make this work, but I do know the Infragistics have a great support forum so it may be worth asking there question there too.
http://forums.infragistics.com/

How do I load user controls dynamically?

How can I load a user control[s] in a window dynamically (using code at runtime)?
I'd highly recommend having a look at Prism, since composite user interfaces is what it's for. However, since this would require you refactoring your entire application, I'll also answer your question directly.
If you want a single user control in a container, put a ContentControl in your XAML and then set the Content property. If you are using a view model, you could bind Content to a FrameworkElement property on the view model:
contentControlInstance.Content = new CustomUserControl();
If you want multiple controls in a list, use an ItemsControl and assign an ObservableCollection<> to the ItemsSource property. If you are using a view model, you could bind ItemsSource to an ObservableCollection property on the View Model.
Then you can just add/remove views from that ObservableCollection:
private ObservableCollection<FrameworkElement> views =
new ObservableCollection<FrameworkElement>();
private void Initialize()
{
itemsControl.ItemsSource = views;
}
private void AddView(FrameworkElement frameworkElement)
{
views.Add(frameworkElement);
}
For adding multiple controls you need container.
Suppose you have a StackPanel container "myStack"
<Window ..>
<StackPanel Name="MyStack" />
</Window>
You can create control dynamically and add it to container. See code below
void AddButtons()
{
Button B1=new Button(),B2=new Button(), B3=new Button();
B1.Content="Hello";
B2.Content="First";
B3.content="Application";
// Now you can set more properties like height, width, margin etc...
MyStack.Children.Add(B1);
MyStack.Children.Add(B2);
MyStack.Children.Add(B2);
}
Or use binding. Here's a really crude example showing how different WPF controls can be shown in a single WPF window using ContentControl and binding (which is what a toolkit like Prism or Caliburn Micro does).
XAML:
<UserControl x:Class="ViewA">
...
<UserControl/>
<UserControl x:Class="ViewB">
...
<UserControl/>
Code:
void ShowViewModelDialog (object viewModel)
{
var host = new MyViewHost();
FrameworkElement control = null;
string viewModelName = viewModel.GetType().Name;
switch (viewModelName )
{
case ("ViewModelA"):
control = new ViewA();
break;
case ("ViewModelB"):
control = new ViewB();
break;
default:
control = new TextBlock {Text = String.Format ("No view for {0}", viewModelName);
break;
}
if (control!=null) control.DataContext = viewModel;
host.DataContext = control;
host.Show(); // Host window will show either ViewA, ViewB, or TextBlock.
}

Resources