Silverlight 2でコンテキストメニューを表示する

Mike SnowsさんのブログでSilverlightでマウスの右クリックをフックする方法が紹介されていたので、やってみた。

普通はSilverlightコンテンツ上で右クリックすると「Silverlightの構成」というコンテキストメニューが表示されてしまうけど、これをDOMの「oncontextmenu」イベントをフックして表示させないようにするという技。

oncontextmenuイベントをフックするのは単純に「HtmlPage.Document.AttachEvent」メソッドを使ってイベントに関連付ければいい。

Page.xaml.cs
public Page() {
   InitializeComponent();

   HtmlPage.Document.AttachEvent("oncontextmenu", OnContextMenu);
}
void OnContextMenu(object sender, HtmlEventArgs e) {
   e.PreventDefault();
}

でも、これだけではoncontextmenuイベントは呼び出されないのでSilverlightコンテンツをWindowlessモードにする必要がある。

Silverlightコンテンツの埋め込みにSilverlightコントロールを使っている場合はWindowlessプロパティをtrueに設定すればいい。

Default.aspx

<asp:Silverlight ID="Xaml1" runat="server" Windowless="true"
   Source="~/ClientBin/Debug/SilverlightApplication2.xap" MinimumVersion="2.0.30523" Width="100%" Height="100%"
/>

生のHTMLの場合はparamタグで指定する。

<object type="application/x-silverlight-2-b2" data="data:application/x-silverlight-2-b2," id="Xaml1" style="height:100%;width:100%;">
   <param name="MinRuntimeVersion" value="2.0.30523"></param>
   <param name="Windowless" value="True"></param>
   <a href="http://go2.microsoft.com/fwlink/?LinkID=114576&amp;v=2.0">
       <img src="http://go2.microsoft.com/fwlink/?LinkID=108181" alt="Microsoft Silverlight を取得" style="border-width:0;" />
   </a>
</object>

これで「Silverlightの構成」というメニューを表示させないようにすることができる。

コンテキストメニューを表示する

せっかくなのでこれを利用して独自のコンテキストメニューを表示してみた。

まずはXAML

Page.xml

<UserControl x:Class="SilverlightApplication2.Page"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Width="400" Height="300">
    
   <UserControl.Resources>
       <Style x:Key="menuItem" TargetType="Button">
           <Setter Property="Template">
               <Setter.Value>
                   <ControlTemplate TargetType="Button">
                       <Grid Height="20">
                           <Border CornerRadius="4" BorderThickness="1">
                               <Border.Background>
                                   <LinearGradientBrush x:Name="brush" StartPoint="0.5,0" EndPoint="0.5,1">
                                       <GradientStop Color="White" Offset="0" />
                                       <GradientStop x:Name="background" Color="White" Offset="0.45" />
                                   </LinearGradientBrush>
                               </Border.Background>
                               <Border.BorderBrush>
                                   <SolidColorBrush x:Name="border" />
                               </Border.BorderBrush>
                           </Border>
                            
                           <ContentPresenter VerticalAlignment="Center" Margin="4,0,0,0" />
                            
                           <VisualStateManager.VisualStateGroups>
                               <VisualStateGroup x:Name="CommonState">
                                   <VisualState x:Name="Normal" />
                                   <VisualState x:Name="MouseOver">
                                       <Storyboard>
                                           <ColorAnimation To="Orange" Duration="0:0:0"
                                                           Storyboard.TargetName="background" Storyboard.TargetProperty="Color" />
                                           <ColorAnimation To="Orange" Duration="0:0:0"
                                                           Storyboard.TargetName="border" Storyboard.TargetProperty="Color" />
                                            
                                           <DoubleAnimation To="0.65" Duration="0:0:0"
                                                            Storyboard.TargetName="brush" Storyboard.TargetProperty="Opacity" />
                                       </Storyboard>
                                   </VisualState>
                               </VisualStateGroup>
                           </VisualStateManager.VisualStateGroups>
                       </Grid>
                   </ControlTemplate>
               </Setter.Value>
           </Setter>
       </Style>
   </UserControl.Resources>
    
   <Grid x:Name="LayoutRoot" Background="White" MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown">
       <Popup x:Name="contextMenu">
           <Border Width="120" Height="64" BorderBrush="Silver" BorderThickness="1">
               <StackPanel Margin="1">
                   <Button Content="コピー" Click="Button_Click" Style="{StaticResource menuItem}" />
                   <Button Content="貼り付け" Click="Button_Click" Style="{StaticResource menuItem}" />
                   <Button Content="切り取り" Click="Button_Click" Style="{StaticResource menuItem}" />
               </StackPanel>
           </Border>
       </Popup>
   </Grid>
    
</UserControl>

コードビハインド側

Page.xaml.cs

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Browser;
using System.Windows.Controls;

namespace SilverlightApplication2 {
   public partial class Page : UserControl {
       public Page() {
           InitializeComponent();

           HtmlPage.Document.AttachEvent("oncontextmenu", OnContextMenu);
       }

       void OnContextMenu(object sender, HtmlEventArgs e) {
           e.PreventDefault();

           contextMenu.HorizontalOffset = e.OffsetX;
           contextMenu.VerticalOffset = e.OffsetY;
           contextMenu.IsOpen = true;
       }

       private void Button_Click(object sender, RoutedEventArgs e) {
           contextMenu.IsOpen = false;

           HtmlPage.Window.Alert(
               string.Format("{0} click!", ((Button)sender).Content.ToString())
           );
       }

       private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
           contextMenu.IsOpen = false;
       }

   }
}

やってることは単純なことなので解説は割愛。

これを実行して右クリックすると以下のように表示される。

Windowlessにすることがどれくらいの影響があるのかわからないけど、問題が無ければこのテクニックを使ってもいいと思う。