2011年8月22日月曜日

WF 標準アクティビティのアイコン

以前はアクティビティのインスタンスから逆に抽出して ToolboxIconWrapper クラスに指定するという、力技この上ない方法にて標準アクティビティのアイコンを表示していました。ところが最近、海外 MSDN Blog にて正式版とも言える方法が記載されていたので試してみました。
元々の MSDN Blog では C# 版のソースだったので、ここでは VB 版です。

   1: Public Class ToolboxIconExtraction
   2:
   3:     Private Shared iconsDict As ResourceDictionary = Nothing
   4:  
   5:     Friend Overloads Shared Function ExtractIconResource(ByVal iconOrtypeName As String) As Object
   6:         If iconsDict Is Nothing Then
   7:             iconsDict = New ResourceDictionary With
   8:                       {
   9:                           .Source = New Uri("pack://application:,,,/System.Activities.Presentation;component/themes/icons.xaml")
  10:                       }
  11:         End If
  12:  
  13:         Dim resourceKey = GetResourceName(iconOrtypeName)
  14:         Dim resource As Object = Nothing
  15:         If iconsDict.Contains(resourceKey) Then resource = iconsDict(resourceKey)
  16:         If Not TypeOf resource Is DrawingBrush Then resource = iconsDict("GenericLeafActivityIcon")
  17:  
  18:         Return resource
  19:     End Function
  20:  
  21:     Friend Overloads Shared Function ExtractIconResource(ByVal targetType As Type) As Object
  22:         Dim typeName = ""
  23:         If targetType.IsGenericType Then
  24:             typeName = targetType.GetGenericTypeDefinition.Name
  25:         Else
  26:             typeName = targetType.Name
  27:         End If
  28:         Return ExtractIconResource(typeName)
  29:     End Function
  30:  
  31:     Friend Overloads Shared Function GetResourceName(ByVal typeName As String) As String
  32:         Dim resourceKey = typeName
  33:         resourceKey = resourceKey.Split(".").Last
  34:         resourceKey = resourceKey.Split("`").First
  35:  
  36:         If resourceKey = "Flowchart" Then
  37:             resourceKey = "FlowChart"
  38:         End If
  39:         If Not resourceKey.EndsWith("Icon") Then
  40:             resourceKey += "Icon"
  41:         End If
  42:         Return resourceKey
  43:     End Function
  44:  
  45: End Class

まずはアイコン抽出用のクラスを記述します。ポイントは
pack://application:,,,/System.Activities.Presentation;component/themes/icons.xaml
にて提供されているリソースを利用するところです。今までの方法では、Microsoft.VisualStudio.Activities.dll を利用していたりと、権利上問題のある手法でしたが、これは .NET 標準の Dll ですので PC にインストールされているものを利用できます。

   1: Public Class ToolboxItemConverter
   2:     Implements IValueConverter
   3:  
   4:     Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
   5:         If Not TypeOf value Is String Then Return Nothing
   6:         Dim result As DrawingBrush = Nothing
   7:         result = TryCast(ToolboxIconExtraction.ExtractIconResource(value.ToString), DrawingBrush)
   8:         Return result
   9:     End Function
  10:  
  11:     Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
  12:         Throw New NotImplementedException
  13:     End Function
  14:  
  15: End Class

続いては XAML 上に指定する Converter クラスを作成します。このクラスは XAML 上で Toolitem を作成する段階で呼び出され、リソースに格納されているアイコンを抽出、返却します。上記コードでは ToolboxItem の表示名(DisplayName プロパティ)の値からリソースを抽出していますが、そのあたりを変更すれば Type から取得する事も簡単です。Convert メソッドのロジックをちょっと変更すれば済みます。

   1: <sapt:ToolboxControl x:Name="toolbox">
   2:     <sapt:ToolboxControl.Style>
   3:         <Style TargetType="{x:Type sapt:ToolboxControl}">
   4:             <Style.Resources>
   5:                 <local:ToolboxItemConverter x:Key="IconConverter"/>
   6:                 <DataTemplate x:Key="IconTemplate">
   7:                     <Rectangle Width="18" Height="18" Fill="{Binding}"/>
   8:                 </DataTemplate>
   9:                 <DataTemplate x:Key="ToolTemplate" DataType="{x:Type sapt:ToolboxItemWrapper}">
  10:                     <Grid>
  11:                         <Grid.ColumnDefinitions>
  12:                             <ColumnDefinition Width="Auto"/>
  13:                             <ColumnDefinition Width="*" SharedSizeGroup="toolLabel"/>
  14:                         </Grid.ColumnDefinitions>
  15:                         <ContentControl Grid.Column="0"
  16:                     Content="{Binding Path=DisplayName, Converter={StaticResource IconConverter}}"
  17:                     ContentTemplate="{StaticResource IconTemplate}"/>
  18:                         <TextBlock Grid.Column="1" Text="{Binding Path=DisplayName}" Margin="5,1,5,0"/>
  19:                     </Grid>
  20:                 </DataTemplate>
  21:             </Style.Resources>
  22:             <Setter Property="ToolTemplate" Value="{StaticResource ToolTemplate}" />
  23:         </Style>
  24:     </sapt:ToolboxControl.Style>
  25: </sapt:ToolboxControl>

そして XAML 側に記載している TooboxControl を貼り付けている部分を上記のように変更します。この際、local:ToolboxItemConverter の箇所でエラーが出る事がありますが、その際には
xmlns:wd="clr-namespace:Workflow4Designer"
のような形で名前空間を追加し、wd:ToolboxItemConverter ~ として記述してください。

これで「ほとんど」の作業が終了です。特殊なケースにあたる Flowchart 関係の指定も上記ロジックには含まれています……が、もう一つだけ特殊ケースがあります。

TransactedReceiveScope アクティビティがその特殊ケースで、こいつのアイコン名は TransactionReceiveScopeIcon です。Transacted~ではなくTransaction~というミスタイプ感満載ですが、ここはハードコーディングで対応してしまって構わないと思います。

image

これでアイコン周りは大分スッキリです。恐らく追加されたアクティビティに対しては、リソースを追加していってあげれば大丈夫なんじゃないかな、と(試していません)。

0 件のコメント:

コメントを投稿