WebBrowser.NavigateToString を使ったビヘイビアを用意する。

先日のエントリ 「WPF で Google Map API を使ってルート検索してみる(Livet版)」 では、View のコードビハインドで WebBrowser.NavigateToString メソッドを呼び出してたわけですが、昨日 MSDN フォーラム見てたら、以前私がビヘイビア書いてたスレ見つけました。すっかり忘れてた・・・orz


関連記事WPFにはWebBrowser.DocumentTextはないのでしょうか


というわけで、先日のエントリのビヘイビア使ったバージョンです。

ビヘイビア

まずビヘイビアを用意し、WebBrowser.NavigateToString を View のコードビハインド内でコールしなくてもいいようにします。


WebBrowserBehaiviors.vb

Option Explicit On
Option Strict On

Public Class WebBrowserBehaiviors

#Region "DocumentText"
	Public Shared ReadOnly DocumentTextProperty As DependencyProperty =
		DependencyProperty.RegisterAttached(
			"DocumentText",
			GetType(String),
			GetType(WebBrowserBehaiviors),
			New UIPropertyMetadata(String.Empty, AddressOf OnDocumentTextChanged)
		)

	<AttachedPropertyBrowsableForType(GetType(WebBrowser))> _
	Public Shared Function GetDocumentText(obj As DependencyObject) As String
		Return obj.GetValue(DocumentTextProperty).ToString()
	End Function

	<AttachedPropertyBrowsableForType(GetType(WebBrowser))> _
	Public Shared Sub SetDocumentText(obj As DependencyObject, value As String)
		obj.SetValue(DocumentTextProperty, value)
	End Sub

	Private Shared Sub OnDocumentTextChanged(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
		Dim browser = TryCast(sender, WebBrowser)
		If (browser Is Nothing) Then Return
		If (e.NewValue Is Nothing) Then Return
		Dim document = e.NewValue.ToString()
		browser.NavigateToString(document)
	End Sub
#End Region

End Class


Livet 調べるとかなり大量にビヘイビアが用意されていて、WebBrowser 用のビヘイビアも幾つか存在します。ただし WebBrowser.NavigateToString 用のビヘイビアは見当たりませんでしたね。これもそのうち提供されるでしょう・・・と勝手に期待しときますw

Model

モデルは先日のと変わりません。

Customer.vb

Public Class Customer
	Inherits NotificationObject
	Public Property Address As String
End Class


Customers.vb

Imports System.Collections.ObjectModel

Public Class Customers
	Inherits NotificationObject
	Private _customers As ObservableCollection(Of Customer)
	Default Public Property Items(index As Integer) As ObservableCollection(Of Customer)
		Get
			Return _customers
		End Get
		Set(ByVal value As ObservableCollection(Of Customer))
			_customers = value
		End Set
	End Property
End Class

ViewModel

ViewModel はビヘイビアと通信できるよう Document プロパティを追加し、View のコマンドを ViewModel に移動します。

#Region "Document変更通知プロパティ"
	Private _Document As String

	Public Property Document() As String
		Get
			Return _Document
		End Get
		Set(ByVal value As String)
			'If (_Document = value) Then Return
			_Document = value
			RaisePropertyChanged("Document")
		End Set
	End Property
#End Region

・・・・・・・・・・

#Region "NavigateCommand"
	Private _NavigateCommand As ViewModelCommand

	Public ReadOnly Property NavigateCommand() As ViewModelCommand
		Get
			If _NavigateCommand Is Nothing Then
				_NavigateCommand = New ViewModelCommand(AddressOf Navigate, AddressOf CanNavigate)
			End If
			Return _NavigateCommand
		End Get
	End Property

	Private Function CanNavigate() As Boolean
		Return True
	End Function

	Private Sub Navigate()
		Me.Document = GetNavigateString()
	End Sub
#End Region


ViewModel のコード全文です。

MainWindowViewModel.vb

Imports System.Collections.ObjectModel

Public Class MainWindowViewModel
	Inherits ViewModel

#Region "コンストラクタ"
	Public Sub New()
		_Customers.Add(New Customer() With {.Address = "東京駅"})
	End Sub
#End Region

#Region "Address変更通知プロパティ"
	Private _Address As String

	Public Property Address() As String
		Get
			Return _Address
		End Get
		Set(ByVal value As String)
			If (_Address = value) Then Return
			_Address = value
			RaisePropertyChanged("Address")
			Me.AddItemCommand = New ViewModelCommand(AddressOf AddItem, AddressOf CanAddItem)
		End Set
	End Property
#End Region

#Region "Customer変更通知プロパティ"
	Private _Customer As Customer

	Public Property Customer() As Customer
		Get
			Return _Customer
		End Get
		Set(ByVal value As Customer)
			_Customer = value
			RaisePropertyChanged("Customer")
		End Set
	End Property
#End Region

#Region "Customers変更通知プロパティ"
	Private _Customers As New ObservableCollection(Of Customer)

	Public Property Customers() As ObservableCollection(Of Customer)
		Get
			Return _Customers
		End Get
		Set(ByVal value As ObservableCollection(Of Customer))
			_Customers = value
			RaisePropertyChanged("Customers")
		End Set
	End Property
#End Region

#Region "Document変更通知プロパティ"
	Private _Document As String

	Public Property Document() As String
		Get
			Return _Document
		End Get
		Set(ByVal value As String)
			'If (_Document = value) Then Return
			_Document = value
			RaisePropertyChanged("Document")
		End Set
	End Property
#End Region

#Region "AddItemCommand"
	Private _AddItemCommand As ViewModelCommand

	Public Property AddItemCommand() As ViewModelCommand
		Get
			If _AddItemCommand Is Nothing Then
				_AddItemCommand = New ViewModelCommand(AddressOf AddItem, AddressOf CanAddItem)
			End If
			Return _AddItemCommand
		End Get
		Private Set(value As ViewModelCommand)
			_AddItemCommand = value
			RaisePropertyChanged("AddItemCommand")
		End Set
	End Property

	Private Function CanAddItem() As Boolean
		If (String.IsNullOrEmpty(Me.Address)) Then
			Return False
		End If
		Return _Customers.Count <= 25
	End Function

	Private Sub AddItem()
		_Customers.Add(New Customer() With {.Address = Me.Address})
		Me.Address = String.Empty
	End Sub
#End Region

#Region "DeleteCommand"
	Private _DeleteCommand As ViewModelCommand

	Public ReadOnly Property DeleteCommand() As ViewModelCommand
		Get
			If _DeleteCommand Is Nothing Then
				_DeleteCommand = New ViewModelCommand(AddressOf Delete, AddressOf CanDelete)
			End If
			Return _DeleteCommand
		End Get
	End Property

	Private Function CanDelete() As Boolean
		Return True
	End Function

	Private Sub Delete()
		_Customers.Remove(Me.Customer)
	End Sub
#End Region

#Region "NavigateCommand"
	Private _NavigateCommand As ViewModelCommand

	Public ReadOnly Property NavigateCommand() As ViewModelCommand
		Get
			If _NavigateCommand Is Nothing Then
				_NavigateCommand = New ViewModelCommand(AddressOf Navigate, AddressOf CanNavigate)
			End If
			Return _NavigateCommand
		End Get
	End Property

	Private Function CanNavigate() As Boolean
		Return True
	End Function

	Private Sub Navigate()
		Me.Document = GetNavigateString()
	End Sub
#End Region

#Region "メソッド"

	Public Function GetNavigateString() As String
		Dim html As New System.Text.StringBuilder()
		With html
			.AppendLine("<!DOCTYPE html '-//W3C//DTD XHTML 1.0 Strict//EN'  ")
			.AppendLine("  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'> ")
			.AppendLine("<!-- saved from url=(0017)http://localhost/ -->")
			.AppendLine("<html xmlns='http://www.w3.org/1999/xhtml'> ")
			.AppendLine("	<head> ")
			.AppendLine("	<meta http-equiv='content-type' content='text/html; charset=utf-8'/> ")
			.AppendLine("	<script type='text/javascript' src='http://maps.google.com/maps?file=api&key=(key)&sensor=false'></script> ")
			.AppendLine("	<script type='text/javascript'> ")
			.AppendLine("		var map; ")
			.AppendLine("		var directions; ")
			.AppendLine()
			.AppendLine("		function initialize() { ")
			.AppendLine("			if (GBrowserIsCompatible()) { ")
			.AppendLine("				map = new GMap2(document.getElementById('map_canvas')); ")
			.AppendLine("				map.enableScrollWheelZoom(); ")
			.AppendLine("				map.setCenter(new GLatLng(35.681379, 139.765577), 13); ")
			.AppendLine("				directions = new GDirections(map, document.getElementById('route')); ")
			.AppendFormat("				var pointArray = [{0}]; ", GetPointList())
			.AppendLine()
			.AppendLine("				directions.loadFromWaypoints(pointArray,{ locale: 'ja_JP' }); ")
			.AppendLine("			} ")
			.AppendLine("		} ")
			.AppendLine("	</script> ")
			.AppendLine("</head> ")
			.AppendLine("	<body onload='initialize()'> ")
			.AppendLine("		<div id='map_canvas' style='width: 100%; height: 400px'></div> ")
			.AppendLine("		<div id='route' style='width: 100%; height: Auto'></div> ")
			.AppendLine("	</body> ")
			.AppendLine("</html> ")
		End With
		Return html.ToString()
	End Function

	Private Function GetPointList() As String
		Dim ret As String = String.Empty
		For Each item In _customers
			ret += "'" + item.Address + "',"
		Next
		If (Not String.IsNullOrEmpty(ret)) Then
			ret = ret.Substring(0, ret.Length - 1)
		End If
		Return ret
	End Function

#End Region

End Class

View

これでコードビハインドが綺麗になくなりました。MainWindow.xaml.vb も不要になったので Class を Window に変えてしまいます。こうすることでコードビハインドが使用不能になり、強制的に ViewModel を使わざるを得なくなります。

MainWindow.xaml

<Window x:Class="Window"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
		xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
		xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
		xmlns:local="clr-namespace:LivetWPFApplication1"
		Title="MainWindow" Height="630" Width="900"
		WindowStartupLocation="CenterScreen" >
	<Window.DataContext>
		<local:MainWindowViewModel />
	</Window.DataContext>
	<i:Interaction.Triggers>
		<i:EventTrigger EventName="Loaded">
			<i:InvokeCommandAction Command="{Binding NavigateCommand}" />
		</i:EventTrigger>
	</i:Interaction.Triggers>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="8" />
			<RowDefinition Height="135" />
			<RowDefinition Height="8" />
			<RowDefinition />
			<RowDefinition Height="8" />
		</Grid.RowDefinitions>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="8" />
			<ColumnDefinition />
			<ColumnDefinition Width="8" />
		</Grid.ColumnDefinitions>
		<Grid Grid.Column="1" Grid.Row="1" >
			<Grid.ColumnDefinitions>
				<ColumnDefinition />
				<ColumnDefinition Width="8" />
				<ColumnDefinition />
			</Grid.ColumnDefinitions>
			<ListBox Grid.Row="0" Grid.Column="0" DisplayMemberPath="Address"
					 ItemsSource="{Binding Customers}" SelectedValue="{Binding Customer}" >
				<ListBox.InputBindings>
					<KeyBinding Key="Delete" Command="{Binding DeleteCommand}" />
				</ListBox.InputBindings>
			</ListBox>
			<StackPanel Grid.Column="2">
				<Label Height="24" Content="ルート検索する住所を入力してください" />
				<TextBox Height="24" VerticalContentAlignment="Center"
						InputMethod.PreferredImeState="On"
						InputMethod.PreferredImeConversionMode="FullShape,Native" 
						Text="{Binding Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
				<Button Width="80" Height="24" Margin="0,12,0,0" 
						Content="追加"
						HorizontalAlignment="Right"
						Command="{Binding AddItemCommand}"/>
				<Button Width="80" Height="24" Margin="0,12,0,0" 
						Content="検索"
						HorizontalAlignment="Right"
						Command="{Binding NavigateCommand}" />
			</StackPanel>
		</Grid>
		<WebBrowser x:Name="browser" Grid.Row="3" Grid.Column="1"
			 local:WebBrowserBehaiviors.DocumentText="{Binding Document}" />
	</Grid>
</Window>


実行するとこうなる・・・

おまけ

ビヘイビアのサンプルとしてはやや冗長なサンプルになってしまいました。同じ WebBrowser.NavigateToString を使ったビヘイビアのサンプルでは、id:trapemiya さんの方がよりシンプルで判りやすいです。


関連記事Blendのビヘイビアを使った簡単な例。ViewにあるコントロールのメソッドをViewModelから実行する。