views:

386

answers:

2

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:

  1. Get the IsEnabled property of my Back and Forward buttons to correctly bind to the CanGoBack and CanGoForward properties of the WebBrowser.
  2. 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="&lt; Back" />

<Button
    Style="{StaticResource Button_Style}"
    Grid.Column="4"
    IsEnabled="{Binding ElementName=_instructionsWebBrowser, Path=CanGoForward}"
    Command="{Binding GoForwardCommand}"
    Content="Forward &gt;" />

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:

  1. 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?

  2. How do write the GoBackCommand and GoForwardCommand so they are independent of the code-behind and ViewModel and can be declared in markup?

+3  A: 

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);
}
Botz3000
@Botz, thank you. I'm a little hazy on how you actually set up your `FrameworkElement` class. I assume you have a field `webBrowser`, but how do you actually get it to display? I was thinking I'd need to build a `UserControl` and actually place the `WebBrowser` in, say, a `Grid` on the control. How do you handle this with a class that inherits `FrameworkElement`? Thanks.
DanM
You don't actually need a UserControl, a FrameworkElement is just fine as long as you set it up correctly. My implementation might not be the best, but if you want to have a look, the control can be found here: http://pastebin.com/m492dbd3f (if you wonder about BrowserViewModel, it's the ViewModel the control's properties are actually bound to, i'm sure you already have your own ViewModel though)
Botz3000
// btw If you derive from FrameworkElement, you can call AddVisualChild and AddLogicalChild to actually put the WebBrowser or anything else inside it
Botz3000
@Botz, Ahh, nice, thanks. It's working inside my window right now. I just need a clue on how to bind to the NavigationCommands.BrowseBack. I've tried `Command="{Binding ElementName=_instructionsWebBrowser, Path=NavigationCommands.BrowseForward}"` and `Command="{Binding NavigationCommands.BrowseForward}"` so far and had no luck. Thanks again!
DanM
@Botz, I also tried simply `Command="NavigationCommands.BrowseForward"`, but that isn't working either.
DanM
Actually, those are built-in commands. They work anywhere in your application and are bound dynamically to the bindings that are in effect for the Element currently in focus. That also means different controls can each implement this command on their own and depending on which one has the focus, that implementation is used.Just use them like this: `Command="NavigationCommands.BrowseForward"` For things like MenuItems, even the Text is set automatically just by doing that.
Botz3000
Oh! Yeah, one thing was missing. CommandTarget="{Binding ElementName=_instructionsWebBrowser}"
Botz3000
Yes! Works like a charm now. Thanks for all the help :)
DanM
@Botz3000: Would you mind dropping the control back on pastebin for a few days, Thanks.
VoidDweller
A: 

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.

Martin Liversage