Is there an MVVM-friendly way to use the WebBrowser control in WPF? - wpf

Thanks to this question (click me!), I have the Source property of my WebBrowser binding correctly to my ViewModel.
Now I'd like to achieve two more goals:
Get the IsEnabled property of my Back and Forward buttons to correctly bind to the CanGoBack and CanGoForward properties of the WebBrowser.
Figure out how to call the GoForward() and GoBack() methods without resorting to the code-behind and without the ViewModel having to know about the WebBrowser.
I have the following (non-working) XAML markup at the moment:
<WebBrowser
x:Name="_instructionsWebBrowser"
x:FieldModifier="private"
clwm:WebBrowserUtility.AttachedSource="{Binding InstructionsSource}" />
<Button
Style="{StaticResource Button_Style}"
Grid.Column="2"
IsEnabled="{Binding ElementName=_instructionsWebBrowser, Path=CanGoBack}"
Command="{Binding GoBackCommand}"
Content="< Back" />
<Button
Style="{StaticResource Button_Style}"
Grid.Column="4"
IsEnabled="{Binding ElementName=_instructionsWebBrowser, Path=CanGoForward}"
Command="{Binding GoForwardCommand}"
Content="Forward >" />
I'm pretty sure the problem is that CanGoBack and CanGoForward are not dependency properties (and don't implement INotifyChanged), but I'm not quite sure how to get around that.
Questions:
Is there any way to hook up attached properties (as I did with Source) or something similar to get the CanGoBack and CanGoForward bindings to work?
How do write the GoBackCommand and GoForwardCommand so they are independent of the code-behind and ViewModel and can be declared in markup?

For anyone who comes across this question and wants a complete solution, here it is. It combines all of the suggestions made in this thread and the linked threads (and others those link to).
XAML:
http://pastebin.com/aED9pvW8
C# class:
http://pastebin.com/n6cW9ZBB
Example XAML usage:
http://pastebin.com/JpuNrFq8
Note: The example assumes your view binds to a ViewModel that provides the source URL to the browser. A very rudimentary navigation bar with back, forward, and refresh buttons and address bar is provided just for demonstration.
Enjoy. I have set the expiration on those pastebin's to never, so they should be available for as long as pastebin exists.

I used this in my bindable webbrowser wrapper:
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseBack, BrowseBack, CanBrowseBack));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseForward, BrowseForward, CanBrowseForward));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseHome, GoHome, TrueCanExecute));
CommandBindings.Add(new CommandBinding(NavigationCommands.Refresh, Refresh, TrueCanExecute));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseStop, Stop, TrueCanExecute));
Note that I created my bindable webbrowser as FrameworkElement that exposes DependencyProperties and calls methods on the actual browser element, so i can set CommandBindings on it.
That way, you can use the default NavigationCommands in your View.
The used handlers are:
private void CanBrowseBack(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = webBrowser.CanGoBack;
}
private void BrowseBack(object sender, ExecutedRoutedEventArgs e) {
webBrowser.GoBack();
}
private void CanBrowseForward(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = webBrowser.CanGoForward;
}
private void BrowseForward(object sender, ExecutedRoutedEventArgs e) {
webBrowser.GoForward();
}
private void TrueCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; }
private void Refresh(object sender, ExecutedRoutedEventArgs e) {
try { webBrowser.Refresh(); }
catch (Exception ex) { PmsLog.LogException(ex, true); }
}
private void Stop(object sender, ExecutedRoutedEventArgs e) {
mshtml.IHTMLDocument2 doc = WebBrowser.Document as mshtml.IHTMLDocument2;
if (doc != null)
doc.execCommand("Stop", true, null);
}
private void GoHome(object sender, ExecutedRoutedEventArgs e) {
Source = new Uri(Home);
}

Your question seems to imply that in order to correctly implement an MVVM pattern you are not allowed to have any code-behind. But perhaps adding some code-behind to your view will make it much easier to hook it up with your view-model. You can add dependency properties to the view and let it listen for INotifyPropertyChanged events.

Related

Method binding - What if Method is in another Class/Namespace?

Let's say I got the following ComboBox in my XAML:
<ComboBox x:Name="cmbOinkOink" Loaded="cmbOinkOink_Loaded" />
And I have my cmbOinkOink_Loaded method deep in here:
namespace PiggyWPF.Classes.EventHandler
{
class ComboBoxEventHandler
{
public void cmbOinkOink_Loaded(object sender, RoutedEventArgs e)
{
// Do Stuff...
}
}
}
How am I going to tell XAML that the cmbOinkOink_Loaded is going to be found under PiggyWPF.Classes.EventHandler.ComboBoxEventHandler?
I am not sure there is a straight forward way to achieve this behavior from xaml.
but you can do this easily from code behind.
class Control
{
...
public void cmbOinkOink_Loaded(object sender, RoutedEventArgs e)
{
_handlerObject.DoStuff();
}
You will have to forward the method call one way or another, either via the code behind of that XAML that defines the ComboBox or by using e.g. the ExecuteCommandAction from Interactivity, which requires you to provide a command in you original class instead of just a method, ideally static or otherwise easily accessible so you can use x:Static or something similar in the action's XAML.
(Posted on behalf of the OP).
I guess my only way was to add the following code to my main class:
private void cmbOinkOink_Loaded(object sender, RoutedEventArgs e)
{
using (ComboBoxEventHandler cmbEvent = new ComboBoxEventHandler())
{
cmbEvent.cmbOinkOink_Loaded(ref sender, e);
}
}

Windows Phone/Silverlight: check whether a control has input focus

How to know whether a control such as TextBox has the input focus in a Windows Phone Silverlight app?
You have to use FocusManager
bool b = FocusManager.GetFocusedElement() == myTextbox;
There are events like GotFocus and LostFocus for controls.
If you subscribe to these events they automatically get called when your input receives or looses focus
you can use those events for your purpose.
XAML Declaration
<TextBox Name="myTextbox" GotFocus="myTextbox_GotFocus" />
and inside the cs
private void myTextbox_GotFocus(object sender, RoutedEventArgs e)
{
}
private void ContentPanel_LostFocus(object sender, RoutedEventArgs e)
{
}

Silverlight: How to Prevent Routing a MouseMove Event from a Child Canvas to Its Parent Canvas

I have my XAML code:
<Canvas x:Name="mainCanvas" Width="200" Height="150" Background="LightGray"
MouseLeftButtonUp="mainCanvas_MouseLeftButtonUp"
MouseMove="mainCanvas_MouseMove">
<Canvas x:Name="topCanvas" Width="200" Height="100" Background="LightBlue"
MouseLeftButtonUp="topCanvas_MouseLeftButtonUp"
MouseMove="topCanvas_MouseMove">
</Canvas>
</Canvas>
and its code behind:
private void topCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("topCanvas_MouseLeftButtonUp");
e.Handled = true; // This can prevent routing to the mainCanvas
}
private void mainCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("mainCanvas_MouseLeftButtonUp");
}
private void topCanvas_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("topCanvas_MouseMove");
// How to prevent routing to the mainCanvas?
// e.Handled = true does NOT exist in MouseEventArgs
}
private void mainCanvas_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("mainCanvas_MouseMove");
}
My question is already in the comments.
How to prevent routing the MouseMove event from the topCanvas (the child canvas) to the mainCanvas (parent canvas)?
Thanks.
Peter
Try setting the IsHitTestVisible property of your Canvas. With that property set accordingly mouse events will go either "through" your control or will be caught by it.
Hope this is what you need.
You can try comparing e.OriginalSource in mainCanvas's MouseMove Event and exit the Sub if it wasn't originated from the mainCanvas.
private void mainCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (sender != e.OriginalSource)
return;
}
In replying to your comment in a little more detail. According to the UIElement.MouseMove Event MSDN link.
Controls that inherit MouseMove can provide handling for the event
that acts as handler for all instances, by overriding the OnMouseMove
method. As with direct handling of the event, there is no Handled
property available, so OnMouseMove cannot be implemented in such a way
that it suppresses further handling of the event through the Handled
technique.
and this link states:
This event creates an alias for the Mouse.MouseMove attached event for
this class
Which brings us to this link on AttachedEvents which states.
Extensible Application Markup Language (XAML) defines a language
component and type of event called an attached event. The concept of
an attached event enables you to add a handler for a particular event
to an arbitrary element rather than to an element that actually
defines or inherits the event. In this case, neither the object
potentially raising the event nor the destination handling instance
defines or otherwise "owns" the event.
So as I see it, your only option is to code around it.
The functionality is called "Event Bubbling". You can stop it using below code:
jQuery:
event.stopPropagation();
Ref: http://api.jquery.com/event.stopPropagation/
You can also try below code:
e.stopPropagation(); //to prevent event from bubbling up
e.preventDefault(); //then cancel the event (if it's cancelable)
Ref: http://stackoverflow.com/questions/1967537/how-to-stop-event-bubbling-with-jquery-live
Thanks,
Ravi Verma

How to set focus on TextBox in Silverlight 4 out-of-browser popup

I have a simple ChildWindow popup in Silverlight 4 (beta).
Important: This is an out-of-browser application.
i want to auto set focus on a TextBox control when the window opens.
I've tried a couple things :
The following code doesn't seem to do anything. I don't think the control is ready to be focussed after 'Loading'.
private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
{
textBox1.Focus();
}
This works, but its klunky.
private void ChildWindow_GotFocus(object sender, RoutedEventArgs e)
{
if (_firstTime == true) {
textBox1.Focus();
_firstTime = false;
}
}
Isn't there a better way? I always had to do horrible things like this in WinForms but was hoping not to have to anymore.
Note: This similar question is for in browser only. It suggests calling System.Windows.Browser.HtmlPage.Plugin.Focus(); which doesn't work and in fact gives an error when running on Silverlight 4 beta out-of-browser.
I was having the same problem in SilverLight 4 (OOB) and I noticed that the tab sequence would set focus to a control that i could not see. What appears to be happening is the focus is being set to your control (first one in the tab sequence) and then for some reason the focus moves to the ContentControl (name ="content"), which (i think) is the parent of the child window.
ContentControl by default has IsTabStop=true.
see....
Why would I want IsTabStop set to true on a ContentControl?
To set the ContentControl.IsTabStop = false for all ContentControls in your app, add this to your styles.xaml.
<Style TargetType="ContentControl" >
<Setter Property="IsTabStop" Value="false"/>
</Style>
The same issue happens with the tab sequence on the MainPage. This style will also fix this.
You are on the right track. You need to handle for two test cases:
1. Setting the focus in the browser.
2. Setting the focus out of the browser.
Your code you that you showed in the Loaded event will work perfectly fine out of the browser. All that is necessary is to refactor it to handle both cases:
private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
{
if (App.current.IsRunningOutOfBrowser)
{
textBox1.Focus();
}
else
{
System.Windows.Browser.HtmlPage.Plugin.Focus();
textBox1.Focus();
}
}
That should do the trick for you.
Thanks for all the posts but after doing a little research the below thing work for me
in Xamal:
<TextBox VerticalAlignment="Center" HorizontalAlignment="Left" FontFamily="Arial" FontSize="12" Height="25" Width="200" Margin="38,50,0,0" Name="txtUserName" Text="{Binding LoginInfo.UserName,Mode=TwoWay, NotifyOnValidationError=True}" IsTabStop="True" TabIndex="1" ></TextBox>
// Initialiazing Main Part View Model
/// </summary>
/// <param name="mainPartViewModel"></param>
public ChildWindowLoginControl(MainPartViewModel mainPartViewModel)
{
InitializeComponent();
this.DataContext = mainPartViewModel;
System.Windows.Browser.HtmlPage.Plugin.Focus();
this.GotFocus += (s, e) => { txtUserName.Focus(); };
}
I had to use your GotFocus way for Silverlight 3 application written in IronPython when I wanted to set focus in ChildWindow.
I use:
protected override void OnOpened()
{
base.OnOpened();
textBox1.Focus();
}
Thanks for all the post, but i have find the work done through following.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (App.Current.IsRunningOutOfBrowser)
{
txtSalesOrderNo.Focus();
}
else
{
System.Windows.Browser.HtmlPage.Plugin.Focus();
txtSalesOrderNo.Focus();
}
}

Need help handling events of a DataTemplate in the Application.xaml file

I have in my application a data template that has a few buttons.
I want those buttons' even handler to be fired in the current page (I am using this template in many pages) rather than in the Application.xaml.vb/cs file, since I want different actions on each page.
I hope I am clear.
You can use commanding to achieve this. Have the Buttons in the DataTemplate execute specific Commands:
<Button Command="{x:Static MyCommands.SomeCommand}"/>
Then have each view that uses that DataTemplate handle the Command:
<UserControl>
<UserCommand.CommandBindings>
<CommandBinding Command="{x:Static MyCommands.SomeCommand}"
Executed="_someHandler"/>
</UserCommand.CommandBindings>
</UserControl>
EDIT after comments: Once you have created a code-behind for your ResourceDictionary as per these instructions, you can simply connect events in the usual fashion:
In MyResources.xaml:
<ListBox x:Key="myListBoxResource" ItemSelected="_listBox_ItemSelected"/>
Then in MyResources.xaml.cs:
private void _listBox_ItemSelected(object sender, EventArgs e)
{
...
}
If you use events and not commands, then in your Click event handler just write
private void Button_Click(object sender, RoutedEventArgs e)
{
var dataItem = (FrameworkElement)sender).DataContext;
// process dataItem
}

Resources