XamDiagram のノードはデフォルトでマウスドラッグによるリサイズが可能です。
ノードが選択状態になると、四隅にリサイズハンドルが表示され、これらをドラッグすることでノードをリサイズすることができます。

通常はリサイズハンドルは選択中の一つのノードにのみ表示されます。


ここでは、DiagramNode のデフォルトのテンプレートを上書きし、カスタムのリサイズハンドル要素を追加してリサイズハンドルが常時表示されるようにします。

DiagramNode のデフォルトのテンプレートは以下のファイルにて定義を確認することができます。
https://github.com/Infragistics/wpf-resources/blob/main/DefaultStyles/XamDiagram/generic.xaml

DiagramNode のスタイルを以下のように定義します。

<local:ResizeHandleVisibilityConverter x:Key="resizeHandleConv"/>

<Style x:Key="CustomNodeStyle" TargetType="diagramLocal:DiagramNode">
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="diagramLocal:DiagramNode" Resources="{StaticResource NodeShapes}">
				<Grid>
					<Grid x:Name="ShapePanel"/>
					<Grid x:Name="ContentPanel" />
					<Grid x:Name="EditPanel" Visibility="Collapsed" />
					<!--リサイズハンドルを追加する ここから-->
					<Rectangle Width="8" Height="8" Fill="White" Stroke="Black" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="-4,-4,0,0" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource resizeHandleConv}}" PreviewMouseLeftButtonDown="Rectangle_PreviewMouseLeftButtonDown"/>
					<Rectangle Width="8" Height="8" Fill="White" Stroke="Black" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="-4,0,0,-4" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource resizeHandleConv}}" PreviewMouseLeftButtonDown="Rectangle_PreviewMouseLeftButtonDown"/>
					<Rectangle Width="8" Height="8" Fill="White" Stroke="Black" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-4,-4,0" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource resizeHandleConv}}" PreviewMouseLeftButtonDown="Rectangle_PreviewMouseLeftButtonDown"/>
					<Rectangle Width="8" Height="8" Fill="White" Stroke="Black" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-4,-4" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource resizeHandleConv}}" PreviewMouseLeftButtonDown="Rectangle_PreviewMouseLeftButtonDown"/>
					<!--リサイズハンドルを追加する ここまで-->
				</Grid>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>

デフォルトのテンプレートに、リサイズハンドル表示用の Rectangle 要素を四つ追加しています。
Rectangle には Visibility プロパティをコンバーター付きでバインドし、PreviewMouseLeftButtonDown イベントをフックしておきます。また、スタイルには Key 「 CustomNodeStyle 」を設定しておきます。

ResizeHandleVisibilityConverter クラス、および、Rectangle_PreviewMouseLeftButtonDown イベントを以下の実装します。

public class ResizeHandleVisibilityConverter : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
		var node = value as DiagramNode;
		if (node != null)
		{
			var allowResize = node.AllowResize ?? true;
			// ノードがリサイズを許可していない、またはノードが表示されていない場合は、
			// リサイズハンドルを非表示にする
			if (!allowResize || !node.IsInView)
			{
				return Visibility.Collapsed;
			}
		}
		return Visibility.Visible;
	}

	public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
		throw new NotImplementedException();
	}
}
private void Rectangle_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
	xamDiagram1.SelectedItems.Clear();

	var node = Utilities.GetAncestorFromType(e.OriginalSource as DependencyObject, typeof(DiagramNode), true) as DiagramNode;
	if (node != null)
	{
		// クリックされたノードを選択
		node.IsSelected = true;
	}

	Dispatcher.BeginInvoke(DispatcherPriority.Background,
		new Action(() =>
		{
			// overlay 上のクリック位置(overlay 基準)
			var overlay = (UIElement)sender;
			var p = e.GetPosition(overlay);

			// overlay を基準にクリック位置を root 基準に変換
			var root = Window.GetWindow(overlay);
			var pOnRoot = overlay.TranslatePoint(p, root);

			// root 上のクリック位置にある要素を取得
			var hit = VisualTreeHelper.HitTest(root, pOnRoot);
			if (hit?.VisualHit is DependencyObject d)
			{
				var target = Utilities.GetAncestorFromType(d, typeof(DiagramResizeHandle), false) as DiagramResizeHandle;

				if (target != null && target != overlay)
				{
					// DiagramResizeHandle に対して MouseLeftButtonDown を人工的に発火
					var args = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
					{
						RoutedEvent = UIElement.MouseLeftButtonDownEvent,
						Source = target
					};
					target.RaiseEvent(args);
				}
			}
		}
	));
}

最後に、カスタムの DiagramNode のスタイルを XamDiagram の NodeStyle に適用します。
今回の例では XamDiagram に「 Manager 」と「 Employee 」の二つの NodeDefinition が定義されているため、BasedOn を使用してどちらの NodeStyle にも CustomNodeStyle が適用されるようにします。

<ig:XamDiagram x:Name="xamDiagram1" ItemsSource="{Binding Employees}">
.....
	<ig:XamDiagram.NodeDefinitions>
		<ig:NodeDefinition
			TargetType="{x:Type models:Manager}"
			ChildrenMemberPath="Subordinates"
			DisplayMemberPath="Name">
			<ig:NodeDefinition.NodeStyle>
				<Style TargetType="ig:DiagramNode" BasedOn="{StaticResource CustomNodeStyle}">
					<Setter Property="ShapeType" Value="Ellipse"/>
					<Setter Property="AllowResize" Value="{Binding Path=Content.AllowResize, RelativeSource={RelativeSource Self}}" />
				</Style>
			</ig:NodeDefinition.NodeStyle>
		</ig:NodeDefinition>
		<ig:NodeDefinition 
			TargetType="{x:Type models:Employee}"
			DisplayMemberPath="Name">
			<ig:NodeDefinition.NodeStyle>
				<Style TargetType="ig:DiagramNode" BasedOn="{StaticResource CustomNodeStyle}">
					<Setter Property="ShapeType" Value="Rectangle"/>
					<Setter Property="AllowResize" Value="{Binding Path=Content.AllowResize, RelativeSource={RelativeSource Self}}" />
				</Style>
			</ig:NodeDefinition.NodeStyle>
		</ig:NodeDefinition>
	</ig:XamDiagram.NodeDefinitions>
.....
</ig:XamDiagram>

リサイズハンドルがすべてのノードに表示されるようになりました。

また、上記の実装ではカスタムで追加した Rectagle 要素の Visibility プロパティおよび DiagramNode のAllowResize プロパティを、ノードデータの AllowResize プロパティにバインドしています。
これにより、ノードデータの AllowResize が false の場合はリサイズハンドラが非表示となり、リサイズ操作となります。
「 Manager 1 」と「 Employee 4 」をリサイズ不可としました。

Tagged:

製品について

Ultimate UI for WPF