diff --git a/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml b/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml
index c783a5e6827..a4b7ac35aaf 100644
--- a/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml
+++ b/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml
@@ -4,19 +4,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:System="clr-namespace:System;assembly=mscorlib"
- xmlns:p="clr-namespace:Dynamo.Wpf.Properties"
- xmlns:controls="clr-namespace:Dynamo.Controls"
- xmlns:views="clr-namespace:Dynamo.Views"
xmlns:viewModels="clr-namespace:Dynamo.ViewModels"
- xmlns:dynui="clr-namespace:Dynamo.UI.Controls"
- xmlns:fa="http://schemas.fontawesome.io/icons/"
- xmlns:nodes="clr-namespace:Dynamo.Graph.Nodes;assembly=DynamoCore"
- xmlns:utilities="clr-namespace:Dynamo.Wpf.Utilities"
mc:Ignorable="d"
Height="Auto"
Width="Auto"
- Visibility="{Binding IsCollapsed, Converter={StaticResource InverseBoolToVisibilityCollapsedConverter}}"
MouseLeftButtonDown="AnnotationView_OnMouseLeftButtonDown"
MouseRightButtonDown="AnnotationView_OnMouseRightButtonDown"
Canvas.Left="{Binding Left, Mode=TwoWay}"
@@ -24,1046 +15,4 @@
AllowDrop="True"
IsHitTestVisible="True"
d:DataContext="{d:DesignInstance Type=viewModels:AnnotationViewModel}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml.cs b/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml.cs
index 17f4c4c3941..dfced0f934f 100644
--- a/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml.cs
+++ b/src/DynamoCoreWpf/Views/Core/AnnotationView.xaml.cs
@@ -1,22 +1,32 @@
using System;
+using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
using System.Windows.Shapes;
-using System.Linq;
+using System.Windows.Threading;
+using Dynamo.Configuration;
+using Dynamo.Controls;
+using Dynamo.Graph.Annotations;
+using Dynamo.Graph.Nodes;
+using Dynamo.Logging;
using Dynamo.Selection;
using Dynamo.UI;
+using Dynamo.UI.Controls;
using Dynamo.UI.Prompts;
using Dynamo.Utilities;
using Dynamo.ViewModels;
+using Dynamo.Views;
+using Dynamo.Wpf.Utilities;
using DynCmd = Dynamo.Models.DynamoModel;
+using EventTrigger = System.Windows.EventTrigger;
using TextBox = System.Windows.Controls.TextBox;
-using Dynamo.Wpf.Utilities;
-using Dynamo.Graph.Annotations;
-using Dynamo.Logging;
-using Dynamo.Configuration;
+using Thickness = System.Windows.Thickness;
namespace Dynamo.Nodes
{
@@ -25,35 +35,235 @@ namespace Dynamo.Nodes
///
public partial class AnnotationView : IViewModelView
{
+ internal Grid AnnotationGrid;
+ private Grid frozenButtonZoomedOutGrid;
+ private Grid textBlockGrid;
+ private TextBlock groupTextBlock;
+ private TextBlock groupDescriptionTextBlock;
+ private TextBox groupTextBox;
+ private TextBox groupDescriptionTextBox;
+ private ContentControl groupNameControl;
+ private ContentControl groupDescriptionControls;
+ private Border collapsedAnnotationRectangle;
+ private Expander groupExpander;
+ private ItemsControl inputPortControl;
+ private ItemsControl outputPortControl;
+ private Thumb mainGroupThumb;
+
+ private bool _isUpdatingLayout = false;
+
+ //Converters
+ private static readonly ZoomToVisibilityCollapsedConverter _zoomToVisibilityCollapsedConverter = new ZoomToVisibilityCollapsedConverter();
+ private static readonly ListHasMoreThanNItemsToVisibilityConverter _listHasMoreThanNItemsToVisibilityConverter = new ListHasMoreThanNItemsToVisibilityConverter();
+ private static readonly ConnectionStateToBrushConverter _connectionStateToBrushConverter = SharedDictionaryManager.DynamoConvertersDictionary["ConnectionStateToBrushConverter"] as ConnectionStateToBrushConverter;
+ private static readonly ConnectionStateToVisibilityCollapsedConverter _connectionStateToVisibilityCollapsedConverter = new ConnectionStateToVisibilityCollapsedConverter();
+ private static readonly BoolToVisibilityCollapsedConverter _boolToVisibilityCollapsedConverter = new BoolToVisibilityCollapsedConverter();
+ private static readonly AnnotationTextConverter _annotationTextConverter = SharedDictionaryManager.DynamoConvertersDictionary["AnnotationTextConverter"] as AnnotationTextConverter;
+ private static readonly TextForegroundSaturationColorConverter _textForegroundSaturationColorConverter = SharedDictionaryManager.DynamoConvertersDictionary["TextForegroundSaturationColorConverter"] as TextForegroundSaturationColorConverter;
+ private static readonly GroupTitleVisibilityConverter _groupTitleVisibilityConverter = SharedDictionaryManager.DynamoConvertersDictionary["GroupTitleVisibilityConverter"] as GroupTitleVisibilityConverter;
+ private static readonly InverseBooleanToVisibilityCollapsedConverter _inverseBooleanToVisibilityCollapsedConverter = new InverseBooleanToVisibilityCollapsedConverter();
+ private static readonly InverseBooleanConverter _inverseBooleanConverter = new InverseBooleanConverter();
+ private static readonly NestedGroupsLabelConverter _nestedGroupsLabelConverter = new NestedGroupsLabelConverter();
+ private static readonly CollectionHasMoreThanNItemsToBoolConverter _collectionHasMoreThanNItemsToBoolConverter = new CollectionHasMoreThanNItemsToBoolConverter();
+ private static readonly BackgroundConditionEvaluator _backgroundConditionEvaluator = new BackgroundConditionEvaluator();
+ private static readonly ColorToSolidColorBrushConverter _colorToSolidColorBrushConverter = new ColorToSolidColorBrushConverter();
+ private static readonly MenuItemCheckConverter _menuItemCheckConverter = new MenuItemCheckConverter();
+ private static readonly StringToBrushColorConverter _stringToBrushColorConverter = new StringToBrushColorConverter();
+
+ //Styles
+ private static readonly Style _contextMenuSeparatorStyle = SharedDictionaryManager.DynamoModernDictionary["ContextMenuSeparatorStyle"] as Style;
+ private static readonly Style _contextMenuItemFixedWidthStyle = SharedDictionaryManager.DynamoModernDictionary["ContextMenuItemFixedWidthStyle"] as Style;
+ private static readonly Style _menuItemGroupStyle = SharedDictionaryManager.DynamoModernDictionary["MenuItemGroupStyle"] as Style;
+ private static readonly Style _contextMenuStyle = SharedDictionaryManager.DynamoModernDictionary["ContextMenuStyle"] as Style;
+ private static readonly Style _createGenericToolTipLightStyle = CreateGenericToolTipLightStyle();
+ private static readonly Style _dynamoToolTipTopStyle = GetDynamoToolTipTopStyle();
+ private static readonly Style _groupStyleSeparatorStyle = CreateGroupStyleSeparatorStyle();
+ private static readonly Style _colorSelectorListBoxStyle = CreateColorSelectorListBoxStyle();
+ private static readonly Style _colorSelectorListBoxItemStyle = CreateColorSelectorListBoxItemStyle();
+ private static readonly Style _groupResizeThumbStyle = CreateGroupResizeThumbStyle();
+
+ private static readonly StyleSelector _groupStyleItemSelector = new GroupStyleItemSelector();
+
+ //Font
+ private static FontFamily _artifaktElementRegular = SharedDictionaryManager.DynamoModernDictionary["ArtifaktElementRegular"] as FontFamily;
+ private static FontFamily _artifaktElementBold = SharedDictionaryManager.DynamoModernDictionary["ArtifaktElementBold"] as FontFamily;
+
+ //Brushes
+ private static SolidColorBrush _primaryCharcoal300 = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["PrimaryCharcoal300Brush"] as SolidColorBrush;
+ private static SolidColorBrush _midGreyBrush = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["MidGreyBrush"] as SolidColorBrush;
+ private static SolidColorBrush _blue300Brush = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["Blue300Brush"] as SolidColorBrush;
+ private static SolidColorBrush _preferencesWindowButtonColor = SharedDictionaryManager.DynamoModernDictionary["PreferencesWindowButtonColor"] as SolidColorBrush;
+ private static SolidColorBrush _primaryCharcoal100 = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["PrimaryCharcoal100Brush"] as SolidColorBrush;
+ private static SolidColorBrush _nodeContextMenuSeparatorColor = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["NodeContextMenuSeparatorColor"] as SolidColorBrush;
+ private static SolidColorBrush _nodeContextMenuBackgroundHighlight = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["NodeContextMenuBackgroundHighlight"] as SolidColorBrush;
+ private static SolidColorBrush _nodeContextMenuBackground = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["NodeContextMenuBackground"] as SolidColorBrush;
+ private static SolidColorBrush _nodeContextMenuForeground = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["NodeContextMenuForeground"] as SolidColorBrush;
+
+ private static CornerRadius _cornerRadius = new CornerRadius(10, 10, 0, 0);
+
+ //Images
+ private static readonly BitmapImage _frozenLightImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/Annotations/frozen-light-64px.png"));
+ private static readonly BitmapImage _frozenDarkImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/Annotations/frozen-dark-64px.png"));
+ private static readonly BitmapImage _frozenHoverImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/Annotations/frozen-hover-64px.png"));
+ private static readonly BitmapImage _caretDownGreyImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/caret_down_grey_48px.png"));
+ private static readonly BitmapImage _caretDownWhiteImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/caret_down_white_48px.png"));
+ private static readonly BitmapImage _caretDownHoverImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/caret_down_hover_48px.png"));
+ private static readonly BitmapImage _caretUpGreyImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/caret_up_grey_48px.png"));
+ private static readonly BitmapImage _caretUpWhiteImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/caret_up_white_48px.png"));
+ private static readonly BitmapImage _caretUpHoverImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/caret_up_hover_48px.png"));
+
+ private static readonly BitmapImage _warningImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/warning.png"));
+ private static readonly BitmapImage _errorImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/error.png"));
+ private static readonly BitmapImage _menuGreyImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/menu_grey_48px.png"));
+ private static readonly BitmapImage _menuWhiteImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/menu_white_48px.png"));
+ private static readonly BitmapImage _menuHoverImage = new BitmapImage(new Uri("pack://application:,,,/DynamoCoreWpf;component/UI/Images/menu_hover_48px.png"));
+
public AnnotationViewModel ViewModel { get; private set; }
public static DependencyProperty SelectAllTextOnFocus;
+ static AnnotationView()
+ {
+ // Freeze the bitmaps and brushes to improve performance
+ _frozenDarkImage.Freeze();
+ _frozenLightImage.Freeze();
+ _frozenHoverImage.Freeze();
+ _caretDownGreyImage.Freeze();
+ _caretDownWhiteImage.Freeze();
+ _caretDownHoverImage.Freeze();
+ _caretUpGreyImage.Freeze();
+ _caretUpWhiteImage.Freeze();
+ _caretUpHoverImage.Freeze();
+
+ _primaryCharcoal300.Freeze();
+ _midGreyBrush.Freeze();
+ _blue300Brush.Freeze();
+ _preferencesWindowButtonColor.Freeze();
+ _primaryCharcoal100.Freeze();
+ _nodeContextMenuSeparatorColor.Freeze();
+ _nodeContextMenuBackgroundHighlight.Freeze();
+ _nodeContextMenuBackground.Freeze();
+ _nodeContextMenuForeground.Freeze();
+ }
public AnnotationView()
{
- Resources.MergedDictionaries.Add(SharedDictionaryManager.DynamoModernDictionary);
- Resources.MergedDictionaries.Add(SharedDictionaryManager.DynamoColorsAndBrushesDictionary);
- Resources.MergedDictionaries.Add(SharedDictionaryManager.DataTemplatesDictionary);
- Resources.MergedDictionaries.Add(SharedDictionaryManager.DynamoConvertersDictionary);
-
InitializeComponent();
+ InitializeUI();
+
Unloaded += AnnotationView_Unloaded;
Loaded += AnnotationView_Loaded;
DataContextChanged += AnnotationView_DataContextChanged;
- this.GroupTextBlock.SizeChanged += GroupTextBlock_SizeChanged;
+ this.groupTextBlock.SizeChanged += GroupTextBlock_SizeChanged;
- // Because the size of the CollapsedAnnotationRectangle doesn't necessarily change
+ // Because the size of the collapsedAnnotationRectangle doesn't necessarily change
// when going from Visible to collapse (and other way around), we need to also listen
// to IsVisibleChanged. Both of these handlers will set the ModelAreaHeight on the ViewModel
- this.CollapsedAnnotationRectangle.SizeChanged += CollapsedAnnotationRectangle_SizeChanged;
- this.CollapsedAnnotationRectangle.IsVisibleChanged += CollapsedAnnotationRectangle_IsVisibleChanged;
+ this.collapsedAnnotationRectangle.SizeChanged += CollapsedAnnotationRectangle_SizeChanged;
+ this.collapsedAnnotationRectangle.IsVisibleChanged += CollapsedAnnotationRectangle_IsVisibleChanged;
+ }
+
+ private void InitializeUI()
+ {
+ // Create a new NameScope for this view if one doesn't exist
+ if (NameScope.GetNameScope(this) == null)
+ {
+ NameScope.SetNameScope(this, new NameScope());
+ }
+
+ // Create the Grid
+ AnnotationGrid = new Grid
+ {
+ Name = "AnnotationGrid",
+ Height = Double.NaN,
+ IsHitTestVisible = true
+ };
+
+ // Add RowDefinitions
+ AnnotationGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
+ AnnotationGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+
+ // Create and apply the implicit style
+ CreateExpanderStyle();
+
+ AnnotationGrid.Children.Add(CreateFrozenButtonZoomedOutGrid());
+ AnnotationGrid.Children.Add(CreatePersistentBorder());
+ AnnotationGrid.Children.Add(CreateSelectionBorder());
+ AnnotationGrid.Children.Add(CreateNodeHoverBorder());
+ AnnotationGrid.Children.Add(CreateGroupExpander());
+ AnnotationGrid.Children.Add(CreateCollapsedAnnotationRectangle());
+
+ this.RegisterName(groupTextBlock.Name, groupTextBlock);
+ this.RegisterName(groupTextBox.Name, groupTextBox);
+ this.RegisterName(groupNameControl.Name, groupNameControl);
+ this.RegisterName(groupDescriptionTextBlock.Name, groupDescriptionTextBlock);
+ this.RegisterName(groupDescriptionTextBox.Name, groupDescriptionTextBox);
+ this.RegisterName(groupDescriptionControls.Name, groupDescriptionControls);
+ this.RegisterName(groupExpander.Name, groupExpander);
+
+ this.Content = AnnotationGrid;
}
+ #region Events
private void AnnotationView_Unloaded(object sender, RoutedEventArgs e)
{
Loaded -= AnnotationView_Loaded;
DataContextChanged -= AnnotationView_DataContextChanged;
- this.GroupTextBlock.SizeChanged -= GroupTextBlock_SizeChanged;
- this.CollapsedAnnotationRectangle.SizeChanged -= CollapsedAnnotationRectangle_SizeChanged;
- this.CollapsedAnnotationRectangle.IsVisibleChanged -= CollapsedAnnotationRectangle_IsVisibleChanged;
+ if (groupTextBlock != null)
+ groupTextBlock.SizeChanged -= GroupTextBlock_SizeChanged;
+ if (collapsedAnnotationRectangle != null)
+ {
+ collapsedAnnotationRectangle.SizeChanged -= CollapsedAnnotationRectangle_SizeChanged;
+ collapsedAnnotationRectangle.IsVisibleChanged -= CollapsedAnnotationRectangle_IsVisibleChanged;
+ }
+ if (groupTextBox != null)
+ {
+ groupTextBox.IsVisibleChanged -= GroupTextBox_OnIsVisibleChanged;
+ groupTextBox.GotFocus -= GroupTextBox_OnGotFocus;
+ groupTextBox.TextChanged -= GroupTextBox_OnTextChanged;
+ }
+
+ if (groupDescriptionTextBox != null)
+ {
+ groupDescriptionTextBox.IsVisibleChanged -= GroupDescriptionTextBox_OnIsVisibleChanged;
+ groupDescriptionTextBox.GotFocus -= GroupDescriptionTextBox_GotFocus;
+ groupDescriptionTextBox.TextChanged -= GroupDescriptionTextBox_TextChanged;
+ }
+ if (mainGroupThumb != null)
+ {
+ mainGroupThumb.DragDelta -= AnnotationRectangleThumb_DragDelta;
+ mainGroupThumb.MouseEnter -= Thumb_MouseEnter;
+ mainGroupThumb.MouseLeave -= Thumb_MouseLeave;
+ }
+ UnregisterNamesFromScope();
+ }
+ private void UnregisterNamesFromScope()
+ {
+ try
+ {
+ if (groupTextBlock?.Name != null)
+ this.UnregisterName(groupTextBlock.Name);
+
+ if (groupTextBox?.Name != null)
+ this.UnregisterName(groupTextBox.Name);
+
+ if (groupNameControl?.Name != null)
+ this.UnregisterName(groupNameControl.Name);
+
+ if (groupDescriptionTextBlock?.Name != null)
+ this.UnregisterName(groupDescriptionTextBlock.Name);
+
+ if (groupDescriptionTextBox?.Name != null)
+ this.UnregisterName(groupDescriptionTextBox.Name);
+
+ if (groupDescriptionControls?.Name != null)
+ this.UnregisterName(groupDescriptionControls.Name);
+
+ if (groupExpander?.Name != null)
+ this.UnregisterName(groupExpander.Name);
+ }
+ catch (ArgumentException)
+ {
+ // Name was already unregistered or never registered
+ // This can happen during rapid load/unload cycles
+ }
}
private void AnnotationView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
@@ -78,39 +288,10 @@ private void AnnotationView_Loaded(object sender, RoutedEventArgs e)
SetTextMaxWidth();
SetTextHeight();
}
-
ViewModel.UpdateProxyPortsPosition();
- }
- }
-
- //Set the max width of text area based on the width of the longest word in the text
- private void SetTextMaxWidth()
- {
- var words = this.ViewModel.AnnotationText.Split(' ');
- var maxLength = 0;
- string longestWord = words[0];
- foreach (var w in words)
- {
- if (w.Length > maxLength)
- {
- longestWord = w;
- maxLength = w.Length;
- }
+ CreateAndSetContextMenu(AnnotationGrid);
}
-
- var formattedText = new FormattedText(
- longestWord,
- System.Globalization.CultureInfo.CurrentUICulture,
- FlowDirection.LeftToRight,
- new Typeface(this.GroupTextBlock.FontFamily, this.GroupTextBlock.FontStyle, this.GroupTextBlock.FontWeight, this.GroupTextBlock.FontStretch),
- this.GroupTextBlock.FontSize,
- Brushes.Black);
-
- var margin = this.TextBlockGrid.Margin.Right + this.TextBlockGrid.Margin.Left;
-
- this.ViewModel.AnnotationModel.Width = formattedText.Width + margin;
- this.ViewModel.AnnotationModel.TextMaxWidth = formattedText.Width + margin;
}
private void OnNodeColorSelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -179,15 +360,15 @@ private void OnUngroupAnnotation(object sender, RoutedEventArgs e)
/// The instance containing the event data.
private void AnnotationView_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
- if (GroupTextBlock.IsVisible ||
- (!GroupTextBlock.IsVisible && !GroupTextBox.IsVisible))
+ if (groupTextBlock.IsVisible ||
+ (!groupTextBlock.IsVisible && !groupTextBox.IsVisible))
{
ViewModel.SelectAll();
}
//When Textbox is visible,clear the selection. That way, models will not be added to
//dragged nodes one more time. Ref: MAGN-7321
- if (GroupTextBlock.IsVisible && e.ClickCount >= 2)
+ if (groupTextBlock.IsVisible && e.ClickCount >= 2)
{
DynamoSelection.Instance.ClearSelection();
//Set the panning mode to false if a group is in editing mode.
@@ -200,7 +381,7 @@ private void AnnotationView_OnMouseLeftButtonDown(object sender, MouseButtonEven
//When the Zoom * Fontsized factor is less than 7, then
//show the edit window
- if (!GroupTextBlock.IsVisible && e.ClickCount >= 2)
+ if (!groupTextBlock.IsVisible && e.ClickCount >= 2)
{
var editWindow = new EditWindow(ViewModel.WorkspaceViewModel.DynamoViewModel, true)
{
@@ -224,42 +405,67 @@ private void AnnotationView_OnMouseRightButtonDown(object sender, MouseButtonEve
}
///
- /// Handles the OnTextChanged event of the GroupTextBox control.
+ /// Handles the OnTextChanged event of the groupTextBox control.
/// Calculates the height of a Group based on the height of textblock
///
/// The source of the event.
/// The instance containing the event data.
private void GroupTextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
- if (ViewModel is null || !IsLoaded) return;
+ if (ViewModel is null || !IsLoaded || _isUpdatingLayout) return;
- SetTextMaxWidth();
- SetTextHeight();
- if (GroupTextBox.ActualHeight > 0 && GroupTextBox.ActualWidth > 0)
+ _isUpdatingLayout = true;
+
+ // Use Dispatcher.BeginInvoke to batch layout updates
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ SetTextMaxWidth();
+ SetTextHeight();
+ }
+ finally
+ {
+ _isUpdatingLayout = false;
+ }
+ }), DispatcherPriority.Background);
+
+ if (groupTextBox.ActualHeight > 0 && groupTextBox.ActualWidth > 0)
{
ViewModel.WorkspaceViewModel.HasUnsavedChanges = true;
}
+
}
-
///
- /// Handles the SizeChanged event of the GroupTextBlock control.
+ /// Handles the SizeChanged event of the groupTextBlock control.
/// This function calculates the height of a group based on font size
///
/// The source of the event.
/// The instance containing the event data.
private void GroupTextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
{
- if (ViewModel != null && (e.HeightChanged || e.WidthChanged))
+ if (ViewModel != null && (e.HeightChanged || e.WidthChanged) && !_isUpdatingLayout)
{
- SetTextMaxWidth();
- SetTextHeight();
+ _isUpdatingLayout = true;
+
+ // Use Dispatcher.BeginInvoke to batch layout updates
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ SetTextMaxWidth();
+ SetTextHeight();
+ ViewModel.UpdateProxyPortsPosition();
+ }
+ finally
+ {
+ _isUpdatingLayout = false;
+ }
+ }), DispatcherPriority.Background);
}
-
- ViewModel.UpdateProxyPortsPosition();
}
-
///
/// Select the text in textbox
///
@@ -274,7 +480,7 @@ private void GroupTextBox_OnIsVisibleChanged(object sender, DependencyPropertyCh
ViewModel.WorkspaceViewModel.DynamoViewModel.ExecuteCommand(
new DynCmd.UpdateModelValueCommand(
Guid.Empty, ViewModel.AnnotationModel.GUID, "TextBlockText",
- GroupTextBox.Text));
+ groupTextBox.Text));
ViewModel.WorkspaceViewModel.DynamoViewModel.RaiseCanExecuteUndoRedo();
@@ -292,7 +498,7 @@ private void GroupTextBox_OnIsVisibleChanged(object sender, DependencyPropertyCh
/// The instance containing the event data.
private void GroupTextBox_OnGotFocus(object sender, RoutedEventArgs e)
{
- this.GroupTextBox.CaretIndex = Int32.MaxValue;
+ this.groupTextBox.CaretIndex = Int32.MaxValue;
}
///
@@ -337,7 +543,7 @@ private void GroupDescriptionTextBox_OnIsVisibleChanged(object sender, Dependenc
ViewModel.WorkspaceViewModel.DynamoViewModel.ExecuteCommand(
new DynCmd.UpdateModelValueCommand(
Guid.Empty, ViewModel.AnnotationModel.GUID, nameof(AnnotationModel.AnnotationDescriptionText),
- GroupDescriptionTextBox.Text));
+ groupDescriptionTextBox.Text));
ViewModel.WorkspaceViewModel.DynamoViewModel.RaiseCanExecuteUndoRedo();
@@ -350,7 +556,7 @@ private void GroupDescriptionTextBox_OnIsVisibleChanged(object sender, Dependenc
private void GroupDescriptionTextBox_GotFocus(object sender, RoutedEventArgs e)
{
- this.GroupDescriptionTextBox.CaretIndex = Int32.MaxValue;
+ this.groupDescriptionTextBox.CaretIndex = Int32.MaxValue;
}
private void GroupDescriptionTextBox_TextChanged(object sender, TextChangedEventArgs e)
@@ -358,27 +564,13 @@ private void GroupDescriptionTextBox_TextChanged(object sender, TextChangedEvent
if (ViewModel is null || !IsLoaded) return;
SetTextHeight();
- if (GroupDescriptionTextBox.ActualHeight > 0 && GroupDescriptionTextBox.ActualWidth > 0)
+ if (groupDescriptionTextBox.ActualHeight > 0 && groupDescriptionTextBox.ActualWidth > 0)
{
ViewModel.WorkspaceViewModel.HasUnsavedChanges = true;
}
}
- private void SetTextHeight()
- {
- if (GroupDescriptionTextBlock is null || GroupTextBlock is null || ViewModel is null)
- {
- return;
- }
-
- // Use the DesiredSize and not the Actual height. Because when Textblock is collapsed,
- // Actual height is same as previous size.
- ViewModel.AnnotationModel.TextBlockHeight =
- this.GroupDescriptionControls.DesiredSize.Height +
- this.GroupNameControl.DesiredSize.Height;
- }
-
private void CollapsedAnnotationRectangle_SizeChanged(object sender, SizeChangedEventArgs e)
{
SetModelAreaHeight();
@@ -389,17 +581,6 @@ private void CollapsedAnnotationRectangle_IsVisibleChanged(object sender, Depend
SetModelAreaHeight();
}
- private void SetModelAreaHeight()
- {
- // We only want to change the ModelAreaHeight
- // if the CollapsedAnnotationRectangle is visible,
- // as if its not it will be equal to the height of the
- // contained nodes + the TextBlockHeight
- if (ViewModel is null || !this.CollapsedAnnotationRectangle.IsVisible) return;
- ViewModel.ModelAreaHeight = this.CollapsedAnnotationRectangle.ActualHeight;
- ViewModel.AnnotationModel.UpdateBoundaryFromSelection();
- }
-
private void contextMenu_Click(object sender, RoutedEventArgs e)
{
ViewModel.SelectAll();
@@ -475,5 +656,2228 @@ private void GroupStyleAnnotation_SubmenuOpened(object sender, RoutedEventArgs e
// Tracking loading group style items
Logging.Analytics.TrackEvent(Actions.Load, Categories.GroupStyleOperations, nameof(GroupStyleItem) + "s");
}
+
+ #endregion
+
+ #region Control Templates
+
+ private ControlTemplate CreateGroupExpanderTemplate()
+ {
+ var template = new ControlTemplate(typeof(ToggleButton));
+
+ // Create Border
+ var borderFactory = new FrameworkElementFactory(typeof(Border), "expanderBorder");
+ borderFactory.SetValue(Border.BackgroundProperty, new TemplateBindingExtension(Control.BackgroundProperty));
+ borderFactory.SetValue(Border.BorderBrushProperty, new TemplateBindingExtension(Control.BorderBrushProperty));
+ borderFactory.SetValue(Border.BorderThicknessProperty, new TemplateBindingExtension(Control.BorderThicknessProperty));
+ borderFactory.SetValue(Border.PaddingProperty, new TemplateBindingExtension(Control.PaddingProperty));
+
+ // Create Grid
+ var gridFactory = new FrameworkElementFactory(typeof(Grid));
+
+ // Add ColumnDefinition
+ var columnDef = new FrameworkElementFactory(typeof(ColumnDefinition));
+ columnDef.SetValue(ColumnDefinition.WidthProperty, GridLength.Auto);
+ gridFactory.AppendChild(columnDef);
+
+ // Add Rectangle
+ var rectangleFactory = new FrameworkElementFactory(typeof(Rectangle));
+ rectangleFactory.SetValue(Rectangle.FillProperty, Brushes.Transparent);
+ rectangleFactory.SetValue(Grid.ColumnSpanProperty, 1);
+ gridFactory.AppendChild(rectangleFactory);
+
+ // Add Image
+ var imageFactory = new FrameworkElementFactory(typeof(Image), "sign");
+ imageFactory.SetValue(Image.SourceProperty, _caretDownGreyImage);
+ imageFactory.SetValue(FrameworkElement.WidthProperty, 16.0);
+ imageFactory.SetValue(FrameworkElement.HeightProperty, 16.0);
+ gridFactory.AppendChild(imageFactory);
+
+ // Set Grid as Border's child
+ borderFactory.AppendChild(gridFactory);
+
+ // Set Border as template's visual tree
+ template.VisualTree = borderFactory;
+
+ // Add triggers
+ AddExpanderTriggers(template);
+
+ return template;
+ }
+
+ private void AddExpanderTriggers(ControlTemplate template)
+ {
+ // First MultiDataTrigger
+ var trigger1 = new MultiDataTrigger();
+ trigger1.Conditions.Add(new Condition(
+ new Binding("IsMouseOver") { RelativeSource = RelativeSource.Self },
+ false));
+ trigger1.Conditions.Add(new Condition(
+ new Binding("IsChecked") { RelativeSource = RelativeSource.Self },
+ false));
+ trigger1.Conditions.Add(new Condition(
+ new Binding("Background") { Converter = _backgroundConditionEvaluator },
+ false));
+ trigger1.Setters.Add(new Setter(Image.SourceProperty, _caretDownGreyImage, "sign"));
+
+ // Second MultiDataTrigger
+ var trigger2 = new MultiDataTrigger();
+ trigger2.Conditions.Add(new Condition(
+ new Binding("IsMouseOver") { RelativeSource = RelativeSource.Self },
+ false));
+ trigger2.Conditions.Add(new Condition(
+ new Binding("IsChecked") { RelativeSource = RelativeSource.Self },
+ false));
+ trigger2.Conditions.Add(new Condition(
+ new Binding("Background") { Converter = _backgroundConditionEvaluator },
+ true));
+ trigger2.Setters.Add(new Setter(Image.SourceProperty, _caretDownWhiteImage, "sign"));
+
+ // Third MultiDataTrigger
+ var trigger3 = new MultiDataTrigger();
+ trigger3.Conditions.Add(new Condition(
+ new Binding("IsMouseOver") { RelativeSource = RelativeSource.Self },
+ true));
+ trigger3.Conditions.Add(new Condition(
+ new Binding("IsChecked") { RelativeSource = RelativeSource.Self },
+ false));
+ trigger3.Setters.Add(new Setter(Image.SourceProperty, _caretDownHoverImage, "sign"));
+
+ // Fourth MultiDataTrigger
+ var trigger4 = new MultiDataTrigger();
+ trigger4.Conditions.Add(new Condition(
+ new Binding("IsMouseOver") { RelativeSource = RelativeSource.Self },
+ false));
+ trigger4.Conditions.Add(new Condition(
+ new Binding("IsChecked") { RelativeSource = RelativeSource.Self },
+ true));
+ trigger4.Conditions.Add(new Condition(
+ new Binding("Background") { Converter = _backgroundConditionEvaluator },
+ false));
+ trigger4.Setters.Add(new Setter(Image.SourceProperty, _caretUpGreyImage, "sign"));
+
+ // Fifth MultiDataTrigger
+ var trigger5 = new MultiDataTrigger();
+ trigger5.Conditions.Add(new Condition(
+ new Binding("IsMouseOver") { RelativeSource = RelativeSource.Self },
+ false));
+ trigger5.Conditions.Add(new Condition(
+ new Binding("IsChecked") { RelativeSource = RelativeSource.Self },
+ true));
+ trigger5.Conditions.Add(new Condition(
+ new Binding("Background") { Converter = _backgroundConditionEvaluator },
+ true));
+ trigger5.Setters.Add(new Setter(Image.SourceProperty, _caretUpWhiteImage, "sign"));
+
+ // Sixth MultiDataTrigger
+ var trigger6 = new MultiDataTrigger();
+ trigger6.Conditions.Add(new Condition(
+ new Binding("IsMouseOver") { RelativeSource = RelativeSource.Self },
+ true));
+ trigger6.Conditions.Add(new Condition(
+ new Binding("IsChecked") { RelativeSource = RelativeSource.Self },
+ true));
+ trigger6.Setters.Add(new Setter(Image.SourceProperty, _caretUpHoverImage, "sign"));
+
+ // Add all triggers to template
+ template.Triggers.Add(trigger1);
+ template.Triggers.Add(trigger2);
+ template.Triggers.Add(trigger3);
+ template.Triggers.Add(trigger4);
+ template.Triggers.Add(trigger5);
+ template.Triggers.Add(trigger6);
+ }
+
+ private ControlTemplate CreateFrozenButtonZoomedOutTemplate()
+ {
+ var template = new ControlTemplate(typeof(Button));
+
+ var imageFactory = new FrameworkElementFactory(typeof(Image));
+ imageFactory.Name = "FrozenImageZoomedOut";
+ imageFactory.SetValue(Image.WidthProperty, new TemplateBindingExtension(Button.WidthProperty));
+ imageFactory.SetValue(Image.HeightProperty, new TemplateBindingExtension(Button.HeightProperty));
+ imageFactory.SetValue(Image.SourceProperty, _frozenDarkImage);
+
+ template.VisualTree = imageFactory;
+
+ var multiTrigger = new MultiDataTrigger();
+ multiTrigger.Conditions.Add(new Condition
+ {
+ Binding = new Binding("IsMouseOver")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.Self)
+ },
+ Value = true
+ });
+
+ multiTrigger.Setters.Add(new Setter
+ {
+ TargetName = "FrozenImageZoomedOut",
+ Property = Image.SourceProperty,
+ Value = _frozenHoverImage
+ });
+
+ template.Triggers.Add(multiTrigger);
+
+ return template;
+ }
+
+ private ControlTemplate CreateFrozenButtonZoomedInTemplate()
+ {
+ var template = new ControlTemplate(typeof(Button));
+
+ // Create the Image
+ var imageFactory = new FrameworkElementFactory(typeof(Image));
+ imageFactory.Name = "FrozenImageZoomedIn";
+ imageFactory.SetValue(Image.SourceProperty, _frozenDarkImage);
+
+ template.VisualTree = imageFactory;
+
+ // Create MouseOver trigger
+ var mouseOverTrigger = new MultiDataTrigger();
+ mouseOverTrigger.Conditions.Add(new Condition
+ {
+ Binding = new Binding("IsMouseOver")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.Self)
+ },
+ Value = true
+ });
+ mouseOverTrigger.Setters.Add(new Setter(Image.SourceProperty, _frozenHoverImage, "FrozenImageZoomedIn"));
+
+ // Create Background condition trigger
+ var backgroundTrigger = new MultiDataTrigger();
+ backgroundTrigger.Conditions.Add(new Condition
+ {
+ Binding = new Binding("DataContext.Background")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1),
+ Converter = _backgroundConditionEvaluator
+ },
+ Value = true
+ });
+ backgroundTrigger.Conditions.Add(new Condition
+ {
+ Binding = new Binding("IsMouseOver")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.Self)
+ },
+ Value = false
+ });
+ backgroundTrigger.Setters.Add(new Setter(Image.SourceProperty, _frozenLightImage, "FrozenImageZoomedIn"));
+
+ // Add triggers to template
+ template.Triggers.Add(mouseOverTrigger);
+ template.Triggers.Add(backgroundTrigger);
+
+ return template;
+ }
+ #endregion
+
+ #region Setup UI
+
+ #region Frozen Zoom Button
+
+ private Grid CreateFrozenButtonZoomedOutGrid()
+ {
+ var grid = new Grid
+ {
+ Name = "FrozenButtonGrid"
+ };
+ Grid.SetRow(grid, 0);
+ Grid.SetRowSpan(grid, 2);
+ Grid.SetColumn(grid, 0);
+ Grid.SetColumnSpan(grid, 4);
+ Panel.SetZIndex(grid, 1);
+
+ // Create the Style for the Grid
+ var gridStyle = new Style(typeof(Grid));
+ gridStyle.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Collapsed));
+
+ // MultiDataTrigger
+ var multiTrigger = new MultiDataTrigger();
+
+ // Condition 1: DataContext.AnnotationModel.IsFrozen == true
+ multiTrigger.Conditions.Add(new Condition
+ {
+ Binding = new Binding("DataContext.AnnotationModel.IsFrozen")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1)
+ },
+ Value = true
+ });
+
+ // Condition 2: DataContext.IsExpanded == false
+ multiTrigger.Conditions.Add(new Condition
+ {
+ Binding = new Binding("DataContext.IsExpanded")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1)
+ },
+ Value = false
+ });
+
+ // Condition 3: DataContext.Zoom (with converter) == Visible
+ multiTrigger.Conditions.Add(new Condition
+ {
+ Binding = new Binding("DataContext.Zoom")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(WorkspaceView), 1),
+ Converter = _zoomToVisibilityCollapsedConverter
+ },
+ Value = Visibility.Visible
+ });
+
+ // Setter for the trigger
+ multiTrigger.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Visible));
+
+ // Add the trigger to the style
+ gridStyle.Triggers.Add(multiTrigger);
+
+ // Assign the style to the grid
+ grid.Style = gridStyle;
+
+ // Create the Button
+ var button = new Button
+ {
+ Width = 64,
+ Height = 64,
+ Template = CreateFrozenButtonZoomedOutTemplate(),
+ };
+
+ // Create the ToolTip
+ var toolTip = new ToolTip
+ {
+ Style = _createGenericToolTipLightStyle,
+ Content = new TextBlock
+ {
+ Text = Wpf.Properties.Resources.GroupFrozenButtonToolTip
+ }
+ };
+ button.ToolTip = toolTip;
+ button.SetBinding(Button.CommandProperty, new Binding("ToggleIsFrozenGroupCommand"));
+
+ // Add the Button to the Grid
+ grid.Children.Add(button);
+ frozenButtonZoomedOutGrid = grid;
+ return frozenButtonZoomedOutGrid;
+ }
+
+ #endregion
+
+ #region Borders
+ ///
+ /// Creates a persistent border that will always be visible around the annotation.
+ ///
+ ///
+ private Border CreatePersistentBorder()
+ {
+ var persistentBorder = new Border
+ {
+ Name = "persistentBorder",
+ BorderThickness = new Thickness(2),
+ CornerRadius = _cornerRadius,
+ IsHitTestVisible = false,
+ Margin = new Thickness(-1)
+ };
+
+ // Set Grid properties
+ Grid.SetRowSpan(persistentBorder, 2);
+ Canvas.SetZIndex(persistentBorder, 41);
+
+ persistentBorder.SetBinding(Border.BorderBrushProperty, new Binding("Background"));
+ persistentBorder.SetBinding(Border.VisibilityProperty, new Binding("Nodes")
+ {
+ Converter = _listHasMoreThanNItemsToVisibilityConverter
+ });
+
+ return persistentBorder;
+ }
+
+ ///
+ /// Creates a selection border that will be visible when the annotation is selected.
+ ///
+ ///
+ private Border CreateSelectionBorder()
+ {
+ var selectionBorder = new Border
+ {
+ Name = "selectionBorder",
+ Background = Brushes.Transparent,
+ BorderThickness = new Thickness(2),
+ CornerRadius = _cornerRadius,
+ IsHitTestVisible = false,
+ Margin = new Thickness(-1)
+ };
+
+ Grid.SetRowSpan(selectionBorder, 2);
+ Canvas.SetZIndex(selectionBorder, 41);
+
+ selectionBorder.SetBinding(Border.BorderBrushProperty, new Binding("PreviewState")
+ {
+ UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
+ Mode = BindingMode.OneWay,
+ Converter = _connectionStateToBrushConverter
+ });
+
+ selectionBorder.SetBinding(Border.VisibilityProperty, new Binding("PreviewState")
+ {
+ UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
+ Mode = BindingMode.OneWay,
+ Converter = _connectionStateToVisibilityCollapsedConverter
+ });
+
+ return selectionBorder;
+ }
+
+ ///
+ /// Creates a border that will be visible when the node is being hovered over.
+ /// If a node is dragged over this group (and that node does not already belong to another group)
+ /// this border is activated, indicating that the node can be dropped into the group.
+ /// The visibility of this is controlled by the AnnotationViewModel property 'NodeHoveringState'
+ /// which is set in the StateMachine.
+ ///
+ ///
+ private Border CreateNodeHoverBorder()
+ {
+ var nodeHoverBorder = new Border
+ {
+ Name = "nodeHoveringStateBorder",
+ Background = Brushes.Transparent,
+ BorderBrush = _primaryCharcoal300,
+ BorderThickness = new Thickness(6),
+ CornerRadius = _cornerRadius,
+ IsHitTestVisible = false,
+ Margin = new Thickness(-4)
+ };
+
+ Grid.SetRowSpan(nodeHoverBorder, 2);
+ Canvas.SetZIndex(nodeHoverBorder, 41);
+
+ nodeHoverBorder.SetBinding(Border.VisibilityProperty, new Binding("NodeHoveringState")
+ {
+ Converter = _boolToVisibilityCollapsedConverter
+ });
+
+ return nodeHoverBorder;
+ }
+ #endregion
+
+ #region Group Expander
+
+ private Expander CreateGroupExpander()
+ {
+ groupExpander = new Expander
+ {
+ Name = "GroupExpander"
+ };
+
+ Grid.SetRow(groupExpander, 0);
+
+ groupExpander.SetBinding(FrameworkElement.WidthProperty, new Binding("Width"));
+ groupExpander.SetBinding(Expander.IsExpandedProperty, new Binding("IsExpanded")
+ {
+ Mode = BindingMode.TwoWay
+ });
+
+ textBlockGrid = new Grid
+ {
+ Name = "textBlockGrid",
+ Height = double.NaN, // "Auto" in XAML
+ VerticalAlignment = VerticalAlignment.Top,
+ Margin = new Thickness(5, 0, 5, 0)
+ };
+
+ Grid.SetRow(textBlockGrid, 0);
+
+ textBlockGrid.SetBinding(FrameworkElement.MaxWidthProperty, new Binding("Width"));
+
+ // Add RowDefinitions
+ textBlockGrid.RowDefinitions.Add(new RowDefinition
+ {
+ Name = "GroupNameRow",
+ Height = GridLength.Auto
+ });
+ textBlockGrid.RowDefinitions.Add(new RowDefinition
+ {
+ Name = "GroupDescriptionRow",
+ Height = GridLength.Auto
+ });
+
+ textBlockGrid.Children.Add(CreateGroupNameControl());
+ textBlockGrid.Children.Add(CreateGroupDescriptionControls());
+ SetupGridTriggers();
+
+ groupExpander.Header = textBlockGrid;
+ groupExpander.Content = CreateAnnotationGrid();
+
+ return groupExpander;
+ }
+ private ContentControl CreateGroupNameControl()
+ {
+ groupNameControl = new ContentControl
+ {
+ Name = "groupNameControl",
+ MinHeight = 20,
+ Margin = new Thickness(0, 0, 0, 10)
+ };
+ Grid.SetRow(groupNameControl, 0);
+ groupNameControl.SetBinding(FrameworkElement.WidthProperty, new Binding("Width"));
+
+ var grid = new Grid
+ {
+ Margin = new Thickness(0, 0, 20, 0)
+ };
+
+ //Group name text controls
+ groupTextBlock = new TextBlock
+ {
+ Name = "groupTextBlock",
+ FontFamily = new FontFamily("Trebuchet"),
+ LineStackingStrategy = LineStackingStrategy.BlockLineHeight,
+ TextWrapping = TextWrapping.WrapWithOverflow,
+ Visibility = Visibility.Visible
+ };
+ groupTextBlock.SetBinding(TextBlock.TextProperty, new Binding("AnnotationText")
+ {
+ Converter = _annotationTextConverter,
+ ConverterParameter = "TextBlock"
+ });
+ groupTextBlock.SetBinding(TextBlock.ForegroundProperty, new Binding("Background")
+ {
+ Converter = _textForegroundSaturationColorConverter
+ });
+ groupTextBlock.SetBinding(TextBlock.FontSizeProperty, new Binding("FontSize"));
+
+ groupTextBox = new TextBox
+ {
+ Name = "groupTextBox",
+ FontFamily = new FontFamily("Trebuchet"),
+ TextWrapping = TextWrapping.WrapWithOverflow,
+ Visibility = Visibility.Collapsed,
+ AcceptsReturn = true,
+ AcceptsTab = true
+ };
+ Grid.SetColumn(groupTextBox, 0);
+ groupTextBox.SetBinding(TextBox.TextProperty, new Binding("AnnotationText")
+ {
+ Converter = _annotationTextConverter,
+ ConverterParameter = "TextBox"
+ });
+ groupTextBox.SetBinding(TextBox.FontSizeProperty, new Binding("FontSize"));
+ groupTextBox.IsVisibleChanged += GroupTextBox_OnIsVisibleChanged;
+ groupTextBox.GotFocus += GroupTextBox_OnGotFocus;
+ groupTextBox.TextChanged += GroupTextBox_OnTextChanged;
+
+ // Add controls to the grid
+ grid.Children.Add(groupTextBlock);
+ grid.Children.Add(groupTextBox);
+
+ groupNameControl.Content = grid;
+
+ return groupNameControl;
+ }
+ private ContentControl CreateGroupDescriptionControls()
+ {
+ groupDescriptionControls = new ContentControl
+ {
+ Name = "groupDescriptionControls",
+ MinHeight = 20,
+ Margin = new Thickness(0, -10, 30, 0)
+ };
+
+ Grid.SetRow(groupDescriptionControls, 1);
+
+ // Add event handlers
+ groupDescriptionControls.SizeChanged += GroupDescriptionControls_SizeChanged;
+ groupDescriptionControls.IsVisibleChanged += GroupDescriptionTextBlock_IsVisibleChanged;
+
+ // Create the inner Grid
+ var grid = new Grid();
+
+ groupDescriptionTextBlock = new TextBlock
+ {
+ Name = "groupDescriptionTextBlock",
+ FontFamily = new FontFamily("Trebuchet"),
+ FontSize = 12,
+ LineStackingStrategy = LineStackingStrategy.BlockLineHeight,
+ TextWrapping = TextWrapping.Wrap,
+ Visibility = Visibility.Visible
+ };
+ groupDescriptionTextBlock.SetBinding(TextBlock.TextProperty, new Binding("AnnotationDescriptionText")
+ {
+ Converter = _annotationTextConverter
+ });
+ groupDescriptionTextBlock.SetBinding(TextBlock.ForegroundProperty, new Binding("Background")
+ {
+ Converter = _textForegroundSaturationColorConverter
+ });
+
+ groupDescriptionTextBox = new TextBox
+ {
+ Name = "groupDescriptionTextBox",
+ FontFamily = new FontFamily("Trebuchet"),
+ FontSize = 12,
+ TextWrapping = TextWrapping.Wrap,
+ Visibility = Visibility.Collapsed,
+ AcceptsReturn = true,
+ AcceptsTab = true
+ };
+ groupDescriptionTextBox.SetBinding(TextBox.TextProperty, new Binding("AnnotationDescriptionText")
+ {
+ Converter = _annotationTextConverter
+ });
+ groupDescriptionTextBox.IsVisibleChanged += GroupDescriptionTextBox_OnIsVisibleChanged;
+ groupDescriptionTextBox.GotFocus += GroupDescriptionTextBox_GotFocus;
+ groupDescriptionTextBox.TextChanged += GroupDescriptionTextBox_TextChanged;
+
+ var style = new Style(typeof(ContentControl));
+
+ // Add default visibility setter
+ style.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Collapsed));
+
+ // Create DataTrigger for GroupExpander.IsExpanded
+ var dataTrigger = new DataTrigger
+ {
+ Binding = new Binding("IsExpanded") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Expander), 1) },
+ Value = true
+ };
+ dataTrigger.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Visible));
+ style.Triggers.Add(dataTrigger);
+
+ // Add controls to the grid
+ grid.Children.Add(groupDescriptionTextBlock);
+ grid.Children.Add(groupDescriptionTextBox);
+
+ groupDescriptionControls.Content = grid;
+ groupDescriptionControls.Style = style;
+
+ return groupDescriptionControls;
+ }
+
+ private void SetupGridTriggers()
+ {
+ // Add double-click triggers
+ AddTrigger(ContentControl.MouseDoubleClickEvent, "groupNameControl", "groupTextBlock", "groupTextBox", true);
+ AddTrigger(ContentControl.MouseDoubleClickEvent, "groupDescriptionControls", "groupDescriptionTextBlock", "groupDescriptionTextBox", true);
+
+ // Add lost focus triggers
+ AddTrigger(TextBox.LostKeyboardFocusEvent, "groupTextBox", "groupTextBlock", "groupTextBox", false);
+ AddTrigger(TextBox.LostKeyboardFocusEvent, "groupDescriptionTextBox", "groupDescriptionTextBlock", "groupDescriptionTextBox", false);
+ }
+ private Storyboard CreateStoryboard(string textBlockName, string textBoxName, bool isDoubleClick)
+ {
+ var storyboard = new Storyboard();
+
+ // Add animations
+ AddAnimation(storyboard, textBlockName, "TextBlock", isDoubleClick, true);
+ AddAnimation(storyboard, textBoxName, "TextBox", isDoubleClick, true);
+ AddAnimation(storyboard, textBoxName, "TextBox", isDoubleClick, false);
+
+ return storyboard;
+ }
+ private void AddTrigger(RoutedEvent routedEvent, string sourceName, string textBlockName, string textBoxName, bool isDoubleClick)
+ {
+ var trigger = new EventTrigger
+ {
+ RoutedEvent = routedEvent,
+ SourceName = sourceName
+ };
+ var storyboard = CreateStoryboard(textBlockName, textBoxName, isDoubleClick);
+ trigger.Actions.Add(new BeginStoryboard { Storyboard = storyboard });
+ textBlockGrid.Triggers.Add(trigger);
+ }
+ private void AddAnimation(Storyboard storyboard, string targetName, string targetType, bool isDoubleClick, bool isVisibility)
+ {
+ if (isVisibility)
+ {
+ var visibilityAnimation = new ObjectAnimationUsingKeyFrames();
+ Storyboard.SetTargetName(visibilityAnimation, targetName);
+ Storyboard.SetTargetProperty(visibilityAnimation, new PropertyPath($"({targetType}.Visibility)"));
+ visibilityAnimation.Duration = TimeSpan.Zero;
+
+ var keyFrame = new DiscreteObjectKeyFrame
+ {
+ KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero)
+ };
+
+ if (isDoubleClick)
+ {
+ // Use the static converter instance
+ var visibility = targetType == "TextBlock" ?
+ _groupTitleVisibilityConverter.Convert(Visibility.Visible, typeof(Visibility), "FlipTextblock", null) :
+ _groupTitleVisibilityConverter.Convert(Visibility.Visible, typeof(Visibility), "FlipTextbox", null);
+
+ keyFrame.Value = visibility;
+ }
+ else
+ {
+ // For lost focus, we use direct Visibility values
+ keyFrame.Value = targetType == "TextBlock" ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ visibilityAnimation.KeyFrames.Add(keyFrame);
+ storyboard.Children.Add(visibilityAnimation);
+ }
+ else
+ {
+ var focusableAnimation = new BooleanAnimationUsingKeyFrames();
+ Storyboard.SetTargetName(focusableAnimation, targetName);
+ Storyboard.SetTargetProperty(focusableAnimation, new PropertyPath("(TextBox.Focusable)"));
+
+ var focusableKeyFrame = new DiscreteBooleanKeyFrame
+ {
+ KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero),
+ Value = true
+ };
+
+ focusableAnimation.KeyFrames.Add(focusableKeyFrame);
+ storyboard.Children.Add(focusableAnimation);
+ }
+ }
+
+ #endregion
+
+ #region Main Grid
+
+ private Grid CreateAnnotationGrid()
+ {
+ var grid = new Grid();
+
+ // Add the Canvas
+ var annotationRectangle = CreateAnnotationRectangle();
+ grid.Children.Add(annotationRectangle);
+
+ // Add the Resize Thumb
+ mainGroupThumb = CreateResizeThumb();
+ grid.Children.Add(mainGroupThumb);
+
+ return grid;
+ }
+
+ private Canvas CreateAnnotationRectangle()
+ {
+ var canvas = new Canvas
+ {
+ Name = "AnnotationRectangle",
+ IsHitTestVisible = true,
+ Background = Brushes.Transparent,
+ Opacity = 0.5
+ };
+
+ // Set bindings
+ canvas.SetBinding(FrameworkElement.WidthProperty, new Binding("Width"));
+ canvas.SetBinding(FrameworkElement.HeightProperty, new Binding("ModelAreaHeight"));
+ canvas.SetBinding(UIElement.VisibilityProperty, new Binding("IsExpanded")
+ {
+ Converter = _boolToVisibilityCollapsedConverter
+ });
+
+ // Create and add the Path
+ var path = new Path
+ {
+ Stroke = Brushes.Transparent,
+ StrokeThickness = 0
+ };
+
+ var brush = new SolidColorBrush();
+ BindingOperations.SetBinding(brush, SolidColorBrush.ColorProperty,
+ new Binding("Background"));
+ path.Fill = brush;
+
+ var combinedGeometry = new CombinedGeometry
+ {
+ GeometryCombineMode = GeometryCombineMode.Exclude
+ };
+
+ var rectGeometry = new RectangleGeometry();
+ BindingOperations.SetBinding(rectGeometry, RectangleGeometry.RectProperty,
+ new Binding("ModelAreaRect"));
+ combinedGeometry.Geometry1 = rectGeometry;
+
+ var geometryGroup = new GeometryGroup { FillRule = FillRule.EvenOdd };
+
+ BindingOperations.SetBinding(geometryGroup, GeometryGroup.ChildrenProperty,
+ new Binding("NestedGroupsGeometries"));
+
+ combinedGeometry.Geometry2 = geometryGroup;
+
+ path.Data = combinedGeometry;
+
+ canvas.Children.Add(path);
+
+ return canvas;
+ }
+
+ private Thumb CreateResizeThumb()
+ {
+ var thumb = new Thumb();
+ thumb.Name = "ResizeThumb";
+ thumb.DragDelta += AnnotationRectangleThumb_DragDelta;
+
+
+ thumb.Style = _groupResizeThumbStyle;
+ thumb.MouseEnter += Thumb_MouseEnter;
+ thumb.MouseLeave += Thumb_MouseLeave;
+
+ return thumb;
+ }
+
+ private static Style CreateGroupResizeThumbStyle()
+ {
+ var style = new Style(typeof(Thumb));
+
+ // Add basic property setters
+ style.Setters.Add(new Setter(UIElement.SnapsToDevicePixelsProperty, true));
+ style.Setters.Add(new Setter(FrameworkElement.MarginProperty, new Thickness(0, 0, 2.5, 2.5)));
+ style.Setters.Add(new Setter(FrameworkElement.WidthProperty, 10.0));
+ style.Setters.Add(new Setter(FrameworkElement.HeightProperty, 10.0));
+ style.Setters.Add(new Setter(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Right));
+ style.Setters.Add(new Setter(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Bottom));
+
+ // Create the control template
+ var template = new ControlTemplate(typeof(Thumb));
+
+ // Create the Polygon
+ var polygonFactory = new FrameworkElementFactory(typeof(Polygon));
+
+ // Set Points collection
+ var points = new PointCollection
+ {
+ new Point(0, 8),
+ new Point(8, 8),
+ new Point(8, 0)
+ };
+ polygonFactory.SetValue(Polygon.PointsProperty, points);
+
+ // Set Fill binding
+ polygonFactory.SetBinding(Polygon.FillProperty, new Binding("DataContext.Background")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1),
+ Converter = _textForegroundSaturationColorConverter,
+ ConverterParameter = "Thumb"
+ });
+
+ // Set the template's visual tree
+ template.VisualTree = polygonFactory;
+
+ // Add the template setter to the style
+ style.Setters.Add(new Setter(Control.TemplateProperty, template));
+
+ return style;
+ }
+
+ #endregion
+
+ #region Collapsed controls
+
+ private Border CreateCollapsedAnnotationRectangle()
+ {
+ var border = new Border
+ {
+ Name = "collapsedAnnotationRectangle"
+ };
+
+ // Set bindings
+ border.SetBinding(FrameworkElement.WidthProperty, new Binding("Width"));
+ border.SetBinding(FrameworkElement.HeightProperty, new Binding("ModelAreaHeight"));
+ border.SetBinding(UIElement.VisibilityProperty, new Binding("IsExpanded")
+ {
+ ElementName = "GroupExpander",
+ Converter = _inverseBooleanToVisibilityCollapsedConverter
+ });
+
+ // Set Grid.Row
+ Grid.SetRow(border, 1);
+
+ // Set Background
+ var backgroundBrush = new SolidColorBrush();
+ BindingOperations.SetBinding(backgroundBrush, SolidColorBrush.ColorProperty,
+ new Binding("Background"));
+ backgroundBrush.Opacity = 0.5;
+ border.Background = backgroundBrush;
+
+ // Create and set content
+ border.Child = CreateCollapsedMainGrid();
+
+ collapsedAnnotationRectangle = border;
+
+ return collapsedAnnotationRectangle;
+ }
+
+ private Grid CreateCollapsedMainGrid()
+ {
+ var grid = new Grid();
+
+ // Add column definitions
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
+
+ // Add row definitions
+ grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
+ grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+
+ // Add children
+ inputPortControl = CreateInputPortControl();
+ outputPortControl = CreateOutputPortControl();
+ var groupContent = CreateGroupContent();
+
+ grid.Children.Add(inputPortControl);
+ grid.Children.Add(outputPortControl);
+ grid.Children.Add(groupContent);
+
+ return grid;
+ }
+
+ private ItemsControl CreateInputPortControl()
+ {
+ var itemsControl = new ItemsControl
+ {
+ Name = "inputPortControl",
+ HorizontalContentAlignment = HorizontalAlignment.Left,
+ Margin = new Thickness(-25, 10, -25, 10)
+ };
+
+ Panel.SetZIndex(itemsControl, 20);
+ Grid.SetColumn(itemsControl, 0);
+ Grid.SetRow(itemsControl, 0);
+
+ itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding("InPorts"));
+
+ return itemsControl;
+ }
+
+ private ItemsControl CreateOutputPortControl()
+ {
+ var itemsControl = new ItemsControl
+ {
+ Name = "outputPortControl",
+ HorizontalContentAlignment = HorizontalAlignment.Right,
+ Margin = new Thickness(-25, 10, -25, 10)
+ };
+
+ Panel.SetZIndex(itemsControl, 20);
+ Grid.SetColumn(itemsControl, 2);
+ Grid.SetRow(itemsControl, 0);
+
+ itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding("OutPorts"));
+
+ return itemsControl;
+ }
+
+ private Grid CreateGroupContent()
+ {
+ var grid = new Grid
+ {
+ Name = "GroupContent"
+ };
+
+ Grid.SetRow(grid, 1);
+ Grid.SetColumnSpan(grid, 3);
+
+ // Add column definitions
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+
+ // Add children
+ var nestedGroupsLabel = CreateNestedGroupsLabel();
+ var nodeCountBorder = CreateNodeCountBorder();
+
+ grid.Children.Add(nestedGroupsLabel);
+ grid.Children.Add(nodeCountBorder);
+
+ return grid;
+ }
+
+ private Label CreateNestedGroupsLabel()
+ {
+ var label = new Label
+ {
+ Name = "NestedGroups",
+ Margin = new Thickness(0),
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Right
+ };
+
+ Grid.SetColumn(label, 0);
+
+ label.SetBinding(Label.ContentProperty, new Binding("NestedGroups")
+ {
+ Converter = _nestedGroupsLabelConverter
+ });
+
+ ToolTipService.SetShowOnDisabled(label, false);
+ label.SetBinding(ToolTipService.IsEnabledProperty, new Binding("NestedGroups")
+ {
+ Converter = _collectionHasMoreThanNItemsToBoolConverter
+ });
+
+ // Create and set tooltip
+ label.ToolTip = CreateNestedGroupsTooltip();
+
+ return label;
+ }
+
+ private DynamoToolTip CreateNestedGroupsTooltip()
+ {
+ var tooltip = new DynamoToolTip
+ {
+ AttachmentSide = DynamoToolTip.Side.Bottom,
+ Style = _dynamoToolTipTopStyle,
+ Margin = new Thickness(5, 0, 0, 0)
+ };
+
+ var listView = new ListView();
+ listView.SetBinding(ListView.ItemsSourceProperty, new Binding("NestedGroups"));
+
+ // Create and set ListView style
+ var listViewStyle = new Style(typeof(ListView));
+ listViewStyle.Setters.Add(new Setter(ListView.BackgroundProperty, Brushes.Transparent));
+ listViewStyle.Setters.Add(new Setter(ListView.BorderThicknessProperty, new Thickness(0)));
+
+ // Create data trigger
+ var dataTrigger = new DataTrigger
+ {
+ Binding = new Binding("NestedGroups")
+ {
+ Converter = _collectionHasMoreThanNItemsToBoolConverter
+ },
+ Value = true
+ };
+
+ // Create data template
+ var dataTemplate = new DataTemplate(typeof(AnnotationViewModel));
+ var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
+ textBlockFactory.SetBinding(TextBlock.TextProperty, new Binding("AnnotationText"));
+ dataTemplate.VisualTree = textBlockFactory;
+
+ dataTrigger.Setters.Add(new Setter(ListView.ItemTemplateProperty, dataTemplate));
+ listViewStyle.Triggers.Add(dataTrigger);
+
+ listView.Style = listViewStyle;
+ tooltip.Content = listView;
+
+ return tooltip;
+ }
+
+ private Border CreateNodeCountBorder()
+ {
+ var border = new Border
+ {
+ Name = "NodeCount",
+ BorderThickness = new Thickness(1),
+ BorderBrush = new SolidColorBrush(Color.FromRgb(255, 255, 255)),
+ Background = new SolidColorBrush(Color.FromRgb(238, 238, 238)),
+ MinHeight = 32,
+ MinWidth = 32,
+ Width = Double.NaN,
+ CornerRadius = new CornerRadius(32),
+ Margin = new Thickness(10),
+ VerticalAlignment = VerticalAlignment.Bottom,
+ };
+
+ Grid.SetColumn(border, 1);
+
+ // Create and add TextBlock
+ var textBlock = new TextBlock
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ FontSize = 14
+ };
+
+ textBlock.SetBinding(TextBlock.TextProperty, new Binding("NodeContentCount")
+ {
+ Mode = BindingMode.OneWay,
+ StringFormat = "+{0}"
+ });
+
+ border.Child = textBlock;
+
+ return border;
+ }
+
+ #endregion
+
+ #region Context Menu
+
+ private static MenuItem CreateMenuItem
+ (
+ string name = null,
+ string header = null,
+ Binding command = null,
+ string commandParameter = null,
+ bool isCheckable = false,
+ Binding isChecked = null,
+ Binding visibility = null,
+ Binding isEnabled = null,
+ Binding itemsSource = null,
+ string hotkey = null
+ )
+ {
+ MenuItem menuItem = new MenuItem { Header = header, IsCheckable = isCheckable };
+
+ if (!string.IsNullOrWhiteSpace(name)) menuItem.Name = name;
+ if (command != null) menuItem.SetBinding(MenuItem.CommandProperty, command);
+ if (commandParameter != null) menuItem.CommandParameter = commandParameter;
+ if (isChecked != null) menuItem.SetBinding(MenuItem.IsCheckedProperty, isChecked);
+ if (visibility != null) menuItem.SetBinding(UIElement.VisibilityProperty, visibility);
+ if (isEnabled != null) menuItem.SetBinding(UIElement.IsEnabledProperty, isEnabled);
+ if (itemsSource != null) menuItem.SetBinding(ItemsControl.ItemsSourceProperty, itemsSource);
+ if (!string.IsNullOrWhiteSpace(hotkey)) menuItem.InputGestureText = hotkey;
+
+ return menuItem;
+ }
+
+ private void CreateAndSetContextMenu(Grid grid)
+ {
+ var contextMenu = new ContextMenu
+ {
+ Name = "AnnotationContextMenu",
+ Background = _midGreyBrush,
+ Style = _contextMenuStyle,
+ };
+
+ // Delete Annotation
+ var deleteMenuItem = CreateMenuItem(
+ name: "DeleteAnnotation",
+ header: Wpf.Properties.Resources.GroupContextMenuDeleteGroup);
+ deleteMenuItem.Click += OnDeleteAnnotation;
+ contextMenu.Items.Add(deleteMenuItem);
+
+ // Freeze Annotation
+ contextMenu.Items.Add(CreateMenuItem(
+ name: "FreezeAnnotation",
+ header: Wpf.Properties.Resources.GroupContextMenuFreezeGroup,
+ command: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.ToggleIsFrozenGroupCommand))
+ },
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.AnnotationModel.IsFrozen)),
+ Mode = BindingMode.OneWay
+ }));
+
+ // Ungroup Annotation
+ var ungroupMenuItem = CreateMenuItem(
+ name: "UngroupAnnotation",
+ header: Wpf.Properties.Resources.GroupContextMenuUngroup);
+ ungroupMenuItem.Click += OnUngroupAnnotation;
+ contextMenu.Items.Add(ungroupMenuItem);
+
+ // Preview Annotation
+ contextMenu.Items.Add(CreateMenuItem(
+ name: "PreviewAnnotation",
+ header: Wpf.Properties.Resources.GroupContextMenuPreview,
+ command: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.ToggleIsVisibleGroupCommand))
+ },
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.AnnotationModel.IsVisible)),
+ Mode = BindingMode.OneWay
+ }));
+
+ // Ungroup from Selection
+ contextMenu.Items.Add(CreateMenuItem(
+ name: "unGroup_cm",
+ header: Wpf.Properties.Resources.ContextUnGroupFromSelection,
+ command: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.RemoveGroupFromGroupCommand))
+ }));
+
+ // Add Group to Group
+ contextMenu.Items.Add(CreateMenuItem(
+ name: "AddGroupToGroup",
+ header: Wpf.Properties.Resources.GroupContextMenuAddGroupToGroup,
+ command: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.AddGroupToGroupCommand))
+ }));
+
+ // Graph Layout
+ var graphLayoutMenuItem = CreateMenuItem(
+ name: "GraphLayoutAnnotation",
+ header: Wpf.Properties.Resources.GroupContextMenuGraphLayout);
+ graphLayoutMenuItem.Click += OnGraphLayoutAnnotation;
+ contextMenu.Items.Add(graphLayoutMenuItem);
+
+ // Group Style
+ var groupStyleMenuItem = CreateMenuItem(
+ name: "GroupStyleAnnotation",
+ header: Wpf.Properties.Resources.GroupStyleContextAnnotation,
+ itemsSource: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.GroupStyleList))
+ });
+ groupStyleMenuItem.Style = _menuItemGroupStyle;
+ groupStyleMenuItem.ItemContainerStyleSelector = _groupStyleItemSelector;
+ groupStyleMenuItem.SubmenuOpened += GroupStyleAnnotation_SubmenuOpened;
+ groupStyleMenuItem.Resources.Add("GroupStyleSeparatorStyle", _groupStyleSeparatorStyle);
+ groupStyleMenuItem.Resources.Add("GroupStyleItemStyle", CreateGroupStyleItemStyle());
+ contextMenu.Items.Add(groupStyleMenuItem);
+
+ // Color Menu
+ var colorMenuItem = CreateMenuItem(
+ name: "ColorMenuItem",
+ header: Wpf.Properties.Resources.GroupContextMenuColor);
+ colorMenuItem.Style = _contextMenuItemFixedWidthStyle;
+
+ var colorListBox = new ListBox
+ {
+ Height = double.NaN,
+ ItemContainerStyle = _colorSelectorListBoxItemStyle,
+ Style = _colorSelectorListBoxStyle
+ };
+ colorListBox.SelectionChanged += OnNodeColorSelectionChanged;
+
+ foreach (var color in new[]
+ {
+ "#d4b6db", "#ffb8d8", "#ffc999", "#e8f7ad", "#b9f9e1", "#a4e1ff", "#b5b5b5", "#FFFFFF",
+ "#bb87c6", "#ff7bac", "#ffaa45", "#c1d676", "#71c6a8", "#48b9ff", "#848484", "#d8d8d8"
+ })
+ {
+ colorListBox.Items.Add(new Rectangle
+ {
+ Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString(color))
+ });
+ }
+ colorMenuItem.Items.Add(colorListBox);
+ contextMenu.Items.Add(colorMenuItem);
+
+ // Font Size Menu
+ var fontMenuItem = CreateMenuItem(
+ name: "ChangeSize",
+ header: Wpf.Properties.Resources.GroupContextMenuFont);
+ fontMenuItem.Style = _contextMenuItemFixedWidthStyle;
+
+ foreach (var size in new[] { 14, 18, 24, 30, 36, 48, 60, 72, 96 })
+ {
+ fontMenuItem.Items.Add(CreateMenuItem(
+ name: $"FontSize{size}",
+ header: size.ToString(),
+ command: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.ChangeFontSize))
+ },
+ commandParameter: size.ToString(),
+ isChecked: new Binding
+ {
+ Source = ViewModel,
+ Path = new PropertyPath(nameof(ViewModel.FontSize)),
+ Converter = _menuItemCheckConverter,
+ ConverterParameter = size
+ }));
+ }
+ contextMenu.Items.Add(fontMenuItem);
+
+ grid.ContextMenu = contextMenu;
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Setup Styles
+
+ private static Style CreateContextMenuItemStyle()
+ {
+ var style = new Style(typeof(MenuItem));
+
+ // Basic setters
+ style.Setters.Add(new Setter(MenuItem.IsCheckedProperty, new DynamicResourceExtension("IsChecked")));
+ style.Setters.Add(new Setter(MenuItem.HeightProperty, double.NaN)); // Auto
+ style.Setters.Add(new Setter(MenuItem.MinWidthProperty, 250.0));
+ style.Setters.Add(new Setter(MenuItem.WidthProperty, double.NaN)); // Auto
+ style.Setters.Add(new Setter(MenuItem.PaddingProperty, new Thickness(20, 0, 0, 0)));
+
+ // Create the control template
+ var template = new ControlTemplate(typeof(MenuItem));
+
+ // Create the main DockPanel
+ var dockPanelFactory = new FrameworkElementFactory(typeof(DockPanel), "dockPanel");
+ dockPanelFactory.SetValue(DockPanel.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
+ dockPanelFactory.SetValue(Panel.BackgroundProperty, Brushes.Transparent);
+ dockPanelFactory.SetValue(UIElement.SnapsToDevicePixelsProperty, true);
+
+ // Create checkbox (checkmark)
+ var checkBoxFactory = new FrameworkElementFactory(typeof(Label), "checkBox");
+ checkBoxFactory.SetValue(Label.MarginProperty, new Thickness(0, 0, -20, 0));
+ checkBoxFactory.SetValue(Label.HorizontalAlignmentProperty, HorizontalAlignment.Left);
+ checkBoxFactory.SetValue(Label.VerticalAlignmentProperty, VerticalAlignment.Center);
+ checkBoxFactory.SetValue(Label.HorizontalContentAlignmentProperty, HorizontalAlignment.Center);
+ checkBoxFactory.SetValue(Label.VerticalContentAlignmentProperty, VerticalAlignment.Center);
+ checkBoxFactory.SetValue(Label.ContentProperty, "✓");
+ checkBoxFactory.SetValue(DockPanel.DockProperty, Dock.Left);
+ checkBoxFactory.SetValue(Label.FontSizeProperty, 9.0);
+ checkBoxFactory.SetValue(Label.ForegroundProperty, _preferencesWindowButtonColor);
+ checkBoxFactory.SetValue(UIElement.VisibilityProperty, Visibility.Collapsed);
+
+ // Create ContentPresenter
+ var contentPresenterFactory = new FrameworkElementFactory(typeof(ContentPresenter), "ContentPresenter");
+ contentPresenterFactory.SetValue(ContentPresenter.MarginProperty, new TemplateBindingExtension(Control.PaddingProperty));
+ contentPresenterFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
+ contentPresenterFactory.SetValue(ContentPresenter.ContentSourceProperty, "Header");
+ contentPresenterFactory.SetValue(DockPanel.DockProperty, Dock.Left);
+ contentPresenterFactory.SetValue(ContentPresenter.RecognizesAccessKeyProperty, true);
+ contentPresenterFactory.SetValue(UIElement.SnapsToDevicePixelsProperty, new TemplateBindingExtension(UIElement.SnapsToDevicePixelsProperty));
+ contentPresenterFactory.SetValue(TextBlock.ForegroundProperty, _nodeContextMenuForeground);
+
+ // Create submenu arrow
+ var subMenuArrowFactory = new FrameworkElementFactory(typeof(Label), "subMenuArrow");
+ subMenuArrowFactory.SetValue(Label.MarginProperty, new Thickness(0, 0, 15, 7));
+ subMenuArrowFactory.SetValue(Label.PaddingProperty, new Thickness(0));
+ subMenuArrowFactory.SetValue(Label.VerticalAlignmentProperty, VerticalAlignment.Center);
+ subMenuArrowFactory.SetValue(Label.ContentProperty, ">");
+ subMenuArrowFactory.SetValue(DockPanel.DockProperty, Dock.Right);
+ subMenuArrowFactory.SetValue(Label.FontFamilyProperty, _artifaktElementRegular);
+ subMenuArrowFactory.SetValue(Label.FontSizeProperty, 13.0);
+ subMenuArrowFactory.SetValue(Label.ForegroundProperty, _blue300Brush);
+
+ // Create and set ScaleTransform for submenu arrow
+ var scaleTransform = new ScaleTransform(1, 1.5);
+ subMenuArrowFactory.SetValue(Label.RenderTransformProperty, scaleTransform);
+
+ // Create submenu arrow style
+ var arrowStyle = new Style(typeof(Label));
+ arrowStyle.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Hidden));
+
+ var arrowTrigger = new DataTrigger();
+ arrowTrigger.Binding = new Binding("HasItems")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MenuItem), 1)
+ };
+ arrowTrigger.Value = true;
+ arrowTrigger.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Visible));
+ arrowStyle.Triggers.Add(arrowTrigger);
+
+ subMenuArrowFactory.SetValue(FrameworkElement.StyleProperty, arrowStyle);
+
+ // Create Popup for submenu
+ var popupFactory = new FrameworkElementFactory(typeof(Popup), "PART_Popup");
+ popupFactory.SetValue(Popup.AllowsTransparencyProperty, true);
+ popupFactory.SetValue(UIElement.FocusableProperty, false);
+ popupFactory.SetValue(Popup.HorizontalOffsetProperty, 0.0);
+ popupFactory.SetValue(Popup.PlacementProperty, PlacementMode.Right);
+ popupFactory.SetValue(Popup.VerticalOffsetProperty, -2.0);
+ popupFactory.SetBinding(Popup.IsOpenProperty, new Binding("IsSubmenuOpen")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent)
+ });
+
+ // Create Border for Popup
+ var popupBorderFactory = new FrameworkElementFactory(typeof(Border));
+ popupBorderFactory.SetValue(Border.BackgroundProperty, new TemplateBindingExtension(Control.BackgroundProperty));
+ popupBorderFactory.SetValue(Border.BorderBrushProperty, Brushes.Transparent);
+ popupBorderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(0));
+
+ // Create ScrollViewer for submenu
+ var scrollViewerFactory = new FrameworkElementFactory(typeof(ScrollViewer), "SubMenuScrollViewer");
+ scrollViewerFactory.SetValue(ScrollViewer.CanContentScrollProperty, true);
+ scrollViewerFactory.SetValue(FrameworkElement.StyleProperty, new DynamicResourceExtension(new ComponentResourceKey(typeof(FrameworkElement), "MenuScrollViewer")));
+
+ // Create Grid for ItemsPresenter
+ var gridFactory = new FrameworkElementFactory(typeof(Grid));
+ gridFactory.SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Enabled);
+
+ // Create ItemsPresenter
+ var itemsPresenterFactory = new FrameworkElementFactory(typeof(ItemsPresenter), "ItemsPresenter");
+ itemsPresenterFactory.SetValue(Grid.IsSharedSizeScopeProperty, true);
+ itemsPresenterFactory.SetValue(KeyboardNavigation.DirectionalNavigationProperty, KeyboardNavigationMode.Cycle);
+ itemsPresenterFactory.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Cycle);
+ itemsPresenterFactory.SetValue(UIElement.SnapsToDevicePixelsProperty, new TemplateBindingExtension(UIElement.SnapsToDevicePixelsProperty));
+
+ // Build the visual tree
+ gridFactory.AppendChild(itemsPresenterFactory);
+ scrollViewerFactory.AppendChild(gridFactory);
+ popupBorderFactory.AppendChild(scrollViewerFactory);
+ popupFactory.AppendChild(popupBorderFactory);
+
+ // Add all elements to DockPanel
+ dockPanelFactory.AppendChild(checkBoxFactory);
+ dockPanelFactory.AppendChild(contentPresenterFactory);
+ dockPanelFactory.AppendChild(subMenuArrowFactory);
+ dockPanelFactory.AppendChild(popupFactory);
+
+ // Set the template's visual tree
+ template.VisualTree = dockPanelFactory;
+
+ // Add triggers
+ // Disabled trigger
+ var disabledTrigger = new Trigger { Property = UIElement.IsEnabledProperty, Value = false };
+ disabledTrigger.Setters.Add(new Setter(TextBlock.OpacityProperty, 0.5, "ContentPresenter"));
+ template.Triggers.Add(disabledTrigger);
+
+ // MouseOver trigger
+ var mouseOverTrigger = new Trigger { Property = MenuItem.IsMouseOverProperty, Value = true };
+ mouseOverTrigger.Setters.Add(new Setter(TextBlock.ForegroundProperty, Brushes.White, "ContentPresenter"));
+ mouseOverTrigger.Setters.Add(new Setter(DockPanel.BackgroundProperty, _nodeContextMenuBackgroundHighlight, "dockPanel"));
+ template.Triggers.Add(mouseOverTrigger);
+
+ // MouseOut trigger
+ var mouseOutTrigger = new Trigger { Property = MenuItem.IsMouseOverProperty, Value = false };
+ mouseOutTrigger.Setters.Add(new Setter(DockPanel.BackgroundProperty, _nodeContextMenuBackground, "dockPanel"));
+ template.Triggers.Add(mouseOutTrigger);
+
+ // Checked trigger
+ var checkedTrigger = new Trigger { Property = MenuItem.IsCheckedProperty, Value = true };
+ checkedTrigger.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Visible, "checkBox"));
+ checkedTrigger.Setters.Add(new Setter(TextBlock.FontFamilyProperty, _artifaktElementBold, "ContentPresenter"));
+ template.Triggers.Add(checkedTrigger);
+
+ // Add template to style
+ style.Setters.Add(new Setter(Control.TemplateProperty, template));
+
+ return style;
+ }
+
+ private Style CreateGroupStyleItemStyle()
+ {
+ var style = new Style(typeof(MenuItem));
+
+ // Add IsChecked binding
+ style.Setters.Add(new Setter(MenuItem.IsCheckedProperty, new Binding("IsChecked")));
+
+ // Add Click event handler
+ style.Setters.Add(new EventSetter(MenuItem.ClickEvent, new RoutedEventHandler(GroupStyleCheckmark_Click)));
+
+ // Create template
+ var template = new ControlTemplate(typeof(MenuItem));
+
+ // Create StackPanel
+ var stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
+ stackPanelFactory.Name = "groupStyleStackPanel";
+ stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
+ stackPanelFactory.SetValue(FrameworkElement.MinWidthProperty, 150.0);
+ stackPanelFactory.SetValue(Panel.BackgroundProperty, _midGreyBrush);
+
+ // Create Label (Color Picker)
+ var labelFactory = new FrameworkElementFactory(typeof(Label));
+ labelFactory.Name = "buttonColorPicker";
+ labelFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(5, 0, 5, 0));
+ labelFactory.SetValue(Control.FontFamilyProperty, _artifaktElementRegular);
+ labelFactory.SetValue(Control.FontSizeProperty, 14.0);
+ labelFactory.SetValue(FrameworkElement.WidthProperty, 15.0);
+ labelFactory.SetValue(FrameworkElement.HeightProperty, 15.0);
+ labelFactory.SetBinding(Control.BackgroundProperty, new Binding("HexColorString")
+ {
+ Converter = _stringToBrushColorConverter
+ });
+
+ // Create TextBlock
+ var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
+ textBlockFactory.SetBinding(TextBlock.TextProperty, new Binding("Name"));
+ textBlockFactory.SetValue(TextBlock.TextTrimmingProperty, TextTrimming.CharacterEllipsis);
+ textBlockFactory.SetValue(FrameworkElement.MaxWidthProperty, 148.0);
+ textBlockFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(5, 0, 5, 0));
+ textBlockFactory.SetValue(TextBlock.ForegroundProperty, _nodeContextMenuForeground);
+ textBlockFactory.SetValue(TextBlock.FontFamilyProperty, _artifaktElementRegular);
+ textBlockFactory.SetValue(TextBlock.FontSizeProperty, 14.0);
+ textBlockFactory.SetValue(TextBlock.FontWeightProperty, FontWeights.Medium);
+
+ // Build visual tree
+ stackPanelFactory.AppendChild(labelFactory);
+ stackPanelFactory.AppendChild(textBlockFactory);
+ template.VisualTree = stackPanelFactory;
+
+ // Add triggers
+ var mouseOverTrigger = new Trigger
+ {
+ Property = UIElement.IsMouseOverProperty,
+ Value = true
+ };
+ mouseOverTrigger.Setters.Add(new Setter(Panel.BackgroundProperty,
+ _nodeContextMenuBackgroundHighlight, "groupStyleStackPanel"));
+ template.Triggers.Add(mouseOverTrigger);
+
+ var mouseNotOverTrigger = new Trigger
+ {
+ Property = UIElement.IsMouseOverProperty,
+ Value = false
+ };
+ mouseNotOverTrigger.Setters.Add(new Setter(Panel.BackgroundProperty,
+ _nodeContextMenuBackground, "groupStyleStackPanel"));
+ template.Triggers.Add(mouseNotOverTrigger);
+
+ // Add template to style
+ style.Setters.Add(new Setter(Control.TemplateProperty, template));
+
+ return style;
+ }
+
+ private static Style CreateGroupStyleSeparatorStyle()
+ {
+ var style = new Style(typeof(MenuItem));
+
+ var template = new ControlTemplate(typeof(MenuItem));
+
+ // Create outer border
+ var outerBorderFactory = new FrameworkElementFactory(typeof(Border));
+ outerBorderFactory.SetValue(FrameworkElement.HeightProperty, 1.0);
+ outerBorderFactory.SetValue(Border.BackgroundProperty, _midGreyBrush);
+
+ // Create inner border
+ var innerBorderFactory = new FrameworkElementFactory(typeof(Border));
+ innerBorderFactory.SetValue(FrameworkElement.HeightProperty, 1.0);
+ innerBorderFactory.SetValue(FrameworkElement.MaxWidthProperty, 150.0);
+ innerBorderFactory.SetValue(Border.BackgroundProperty, _nodeContextMenuSeparatorColor);
+
+ // Build visual tree
+ outerBorderFactory.AppendChild(innerBorderFactory);
+ template.VisualTree = outerBorderFactory;
+
+ style.Setters.Add(new Setter(Control.TemplateProperty, template));
+
+ return style;
+ }
+
+ private static Style CreateGenericToolTipLightStyle()
+ {
+ // Main Style for ToolTip
+ var style = new Style(typeof(ToolTip));
+ style.Setters.Add(new Setter(System.Windows.Controls.ToolTip.OverridesDefaultStyleProperty, true));
+ style.Setters.Add(new Setter(System.Windows.Controls.ToolTip.MaxWidthProperty, 300.0));
+
+ // ControlTemplate for ToolTip
+ var template = new ControlTemplate(typeof(ToolTip));
+ var popupGrid = new FrameworkElementFactory(typeof(Grid));
+ popupGrid.Name = "PopupGrid";
+
+ var shadowBackground = new FrameworkElementFactory(typeof(Grid));
+ shadowBackground.Name = "ShadowBackground";
+ shadowBackground.SetValue(Grid.BackgroundProperty, Brushes.Transparent);
+
+ // Path (pointer)
+ var pointerPath = new FrameworkElementFactory(typeof(Path));
+ pointerPath.SetValue(Path.WidthProperty, 20.0);
+ pointerPath.SetValue(Path.HeightProperty, 6.0);
+ pointerPath.SetValue(Path.MarginProperty, new Thickness(5, 0, 0, 0));
+ pointerPath.SetValue(Path.HorizontalAlignmentProperty, HorizontalAlignment.Left);
+ pointerPath.SetValue(Path.VerticalAlignmentProperty, VerticalAlignment.Top);
+ pointerPath.SetValue(Path.DataProperty, Geometry.Parse("M0,6 L6,0 12,6Z"));
+ pointerPath.SetValue(Path.FillProperty, Brushes.White);
+ pointerPath.SetValue(Path.StretchProperty, Stretch.None);
+ pointerPath.SetValue(Path.StrokeProperty, Brushes.Gray);
+
+ // Main Border
+ var mainBorder = new FrameworkElementFactory(typeof(Border));
+ mainBorder.SetValue(Border.MarginProperty, new Thickness(0, 5, 7, 7));
+ mainBorder.SetValue(Border.PaddingProperty, new Thickness(10, 8, 10, 8));
+ mainBorder.SetValue(Border.BackgroundProperty, Brushes.White);
+ mainBorder.SetValue(Border.BorderBrushProperty, new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")));
+ mainBorder.SetValue(Border.BorderThicknessProperty, new Thickness(1, 0, 1, 1));
+ mainBorder.SetValue(Border.CornerRadiusProperty, new CornerRadius(3));
+ mainBorder.AppendChild(new FrameworkElementFactory(typeof(ContentPresenter)));
+
+ // Top Border
+ var topBorder = new FrameworkElementFactory(typeof(Border));
+ topBorder.SetValue(Border.HeightProperty, 7.0);
+ topBorder.SetValue(Border.MarginProperty, new Thickness(16, 5, 9, 0));
+ topBorder.SetValue(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
+ topBorder.SetValue(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
+ topBorder.SetValue(Border.BorderBrushProperty, new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")));
+ topBorder.SetValue(Border.BorderThicknessProperty, new Thickness(0, 1, 0, 0));
+ topBorder.SetValue(Border.CornerRadiusProperty, new CornerRadius(0, 0, 3, 0));
+
+ // Left Border
+ var leftBorder = new FrameworkElementFactory(typeof(Border));
+ leftBorder.SetValue(Border.WidthProperty, 6.0);
+ leftBorder.SetValue(Border.HeightProperty, 7.0);
+ leftBorder.SetValue(Border.MarginProperty, new Thickness(0, 5, 0, 0));
+ leftBorder.SetValue(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
+ leftBorder.SetValue(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
+ leftBorder.SetValue(Border.BorderBrushProperty, new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")));
+ leftBorder.SetValue(Border.BorderThicknessProperty, new Thickness(0, 1, 0, 0));
+ leftBorder.SetValue(Border.CornerRadiusProperty, new CornerRadius(3, 0, 0, 0));
+
+ // Compose visual tree
+ shadowBackground.AppendChild(pointerPath);
+ shadowBackground.AppendChild(mainBorder);
+ shadowBackground.AppendChild(topBorder);
+ shadowBackground.AppendChild(leftBorder);
+ popupGrid.AppendChild(shadowBackground);
+ template.VisualTree = popupGrid;
+
+ style.Setters.Add(new Setter(System.Windows.Controls.ToolTip.TemplateProperty, template));
+
+ // TextBlock style for ContentPresenter
+ var textBlockStyle = new Style(typeof(TextBlock));
+ textBlockStyle.Setters.Add(new Setter(TextBlock.TextWrappingProperty, TextWrapping.Wrap));
+ textBlockStyle.Setters.Add(new Setter(TextBlock.FontFamilyProperty, new FontFamily("Artifakt Element Regular"))); // Adjust as needed
+ textBlockStyle.Setters.Add(new Setter(TextBlock.FontSizeProperty, 12.0));
+ textBlockStyle.Setters.Add(new Setter(TextBlock.ForegroundProperty, new SolidColorBrush((Color)ColorConverter.ConvertFromString("#232323"))));
+
+ return style;
+ }
+
+ private static Style GetDynamoToolTipTopStyle()
+ {
+ var infoBubbleEdgeNormalBrush = SharedDictionaryManager.DynamoColorsAndBrushesDictionary["UnSelectedLayoutForeground"] as SolidColorBrush;
+ var infoBubbleBackNormalBrush = Brushes.White;
+
+ Style customTooltipStyle = new Style(typeof(DynamoToolTip));
+
+ // Create the ControlTemplate
+ ControlTemplate toolTipTemplate = new ControlTemplate(typeof(DynamoToolTip));
+ FrameworkElementFactory gridFactory = new FrameworkElementFactory(typeof(Grid));
+
+ // Add RowDefinitions to the Grid
+ FrameworkElementFactory rowDef0 = new FrameworkElementFactory(typeof(RowDefinition));
+ rowDef0.SetValue(RowDefinition.HeightProperty, new GridLength(1, GridUnitType.Auto));
+ gridFactory.AppendChild(rowDef0);
+
+ FrameworkElementFactory rowDef1 = new FrameworkElementFactory(typeof(RowDefinition));
+ rowDef1.SetValue(RowDefinition.HeightProperty, new GridLength(6));
+ gridFactory.AppendChild(rowDef1);
+
+ FrameworkElementFactory columnDef0 = new FrameworkElementFactory(typeof(ColumnDefinition));
+ columnDef0.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));
+ gridFactory.AppendChild(columnDef0);
+
+ FrameworkElementFactory borderFactory = new FrameworkElementFactory(typeof(Border));
+ borderFactory.SetValue(Grid.RowProperty, 0);
+ borderFactory.SetValue(Border.MarginProperty, new Thickness(0, 0, 0, -1));
+ borderFactory.SetValue(Border.BackgroundProperty, infoBubbleBackNormalBrush);
+ borderFactory.SetValue(Border.BorderBrushProperty, infoBubbleEdgeNormalBrush);
+ borderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(1));
+ borderFactory.SetValue(Border.CornerRadiusProperty, new CornerRadius(2));
+ gridFactory.AppendChild(borderFactory);
+
+ var polylineSegment = new PolyLineSegment()
+ {
+ Points = new PointCollection(new List()
+ {
+ new Point(0,0),
+ new Point(5,6),
+ new Point(10,0)
+ })
+ };
+
+ var tooltipPathFigure = new PathFigure()
+ {
+ IsClosed = false,
+ StartPoint = new Point(0, 0)
+ };
+
+ tooltipPathFigure.Segments.Add(polylineSegment);
+
+ var tooltipGeometry = new PathGeometry();
+ tooltipGeometry.Figures.Add(tooltipPathFigure);
+
+ FrameworkElementFactory pathFactory = new FrameworkElementFactory(typeof(Path));
+ pathFactory.SetValue(Path.HorizontalAlignmentProperty, HorizontalAlignment.Center);
+ pathFactory.SetValue(Path.VerticalAlignmentProperty, VerticalAlignment.Center);
+ pathFactory.SetValue(Path.FillProperty, infoBubbleBackNormalBrush);
+ pathFactory.SetValue(Path.StrokeProperty, infoBubbleEdgeNormalBrush);
+ pathFactory.SetValue(Path.StrokeThicknessProperty, 1.0);
+ pathFactory.SetValue(Path.DataProperty, tooltipGeometry);
+ pathFactory.SetValue(Grid.RowProperty, 1);
+ gridFactory.AppendChild(pathFactory);
+
+ FrameworkElementFactory contentPresenterFactory = new FrameworkElementFactory(typeof(ContentPresenter));
+ contentPresenterFactory.SetValue(ContentPresenter.MarginProperty, new Thickness(4));
+ contentPresenterFactory.SetValue(ContentPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Left);
+ contentPresenterFactory.SetValue(ContentPresenter.VerticalAlignmentProperty, VerticalAlignment.Top);
+ contentPresenterFactory.SetValue(TextBlock.FontSizeProperty, 14.0);
+ gridFactory.AppendChild(contentPresenterFactory);
+
+ toolTipTemplate.VisualTree = gridFactory;
+ customTooltipStyle.Setters.Add(new Setter(DynamoToolTip.TemplateProperty, toolTipTemplate));
+
+ return customTooltipStyle;
+ }
+
+ private static Style CreateColorSelectorListBoxStyle()
+ {
+ var style = new Style(typeof(ListBox));
+
+ // Add basic property setters
+ style.Setters.Add(new Setter(UIElement.SnapsToDevicePixelsProperty, true));
+ style.Setters.Add(new Setter(FrameworkElement.OverridesDefaultStyleProperty, true));
+ style.Setters.Add(new Setter(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled));
+ style.Setters.Add(new Setter(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Hidden));
+ style.Setters.Add(new Setter(ScrollViewer.CanContentScrollProperty, true));
+ style.Setters.Add(new Setter(FrameworkElement.WidthProperty, 150.0));
+ style.Setters.Add(new Setter(FrameworkElement.HeightProperty, 54.0));
+
+ // Create the ControlTemplate
+ var template = new ControlTemplate(typeof(ListBox));
+
+ // Create the Grid
+ var gridFactory = new FrameworkElementFactory(typeof(Grid));
+ gridFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 13, 4, 8));
+
+ // Create the ScrollViewer
+ var scrollViewerFactory = new FrameworkElementFactory(typeof(ScrollViewer));
+ scrollViewerFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(0));
+ scrollViewerFactory.SetValue(UIElement.FocusableProperty, false);
+
+ // Create the WrapPanel
+ var wrapPanelFactory = new FrameworkElementFactory(typeof(WrapPanel));
+ wrapPanelFactory.SetValue(Panel.IsItemsHostProperty, true);
+
+ // Build the visual tree
+ scrollViewerFactory.AppendChild(wrapPanelFactory);
+ gridFactory.AppendChild(scrollViewerFactory);
+ template.VisualTree = gridFactory;
+
+ // Add the template setter
+ style.Setters.Add(new Setter(Control.TemplateProperty, template));
+
+ return style;
+ }
+
+ private static Style CreateColorSelectorListBoxItemStyle()
+ {
+ var style = new Style(typeof(ListBoxItem));
+
+ // Add basic setters
+ style.Setters.Add(new Setter(UIElement.SnapsToDevicePixelsProperty, true));
+ style.Setters.Add(new Setter(ListBoxItem.OverridesDefaultStyleProperty, true));
+ style.Setters.Add(new Setter(FrameworkElement.WidthProperty, 18.0));
+ style.Setters.Add(new Setter(FrameworkElement.HeightProperty, 20.0));
+
+ // Create the control template
+ var template = new ControlTemplate(typeof(ListBoxItem));
+
+ // Create the root Grid
+ var grid = new Grid
+ {
+ Margin = new Thickness(0, 0, 4, 5),
+ SnapsToDevicePixels = true,
+ Background = new SolidColorBrush(Colors.Transparent)
+ };
+
+ // Add ContentPresenter
+ var contentPresenter = new ContentPresenter();
+ grid.Children.Add(contentPresenter);
+
+ // Add Border
+ var border = new Border
+ {
+ Name = "Border",
+ Opacity = 0.25,
+ BorderThickness = new Thickness(1),
+ BorderBrush = Brushes.Black,
+ Background = Brushes.Transparent
+ };
+ grid.Children.Add(border);
+
+ // Create visual states
+ var visualStateGroups = VisualStateManager.GetVisualStateGroups(grid);
+
+ // Common States Group
+ var commonStatesGroup = new VisualStateGroup { Name = "CommonStates" };
+
+ // Normal state
+ var normalState = new VisualState { Name = "Normal" };
+ commonStatesGroup.States.Add(normalState);
+
+ // MouseOver state
+ var mouseOverState = new VisualState { Name = "MouseOver" };
+ var mouseOverStoryboard = new Storyboard();
+ var mouseOverAnimation = new DoubleAnimationUsingKeyFrames();
+ Storyboard.SetTargetName(mouseOverAnimation, "Border");
+ Storyboard.SetTargetProperty(mouseOverAnimation, new PropertyPath(Border.OpacityProperty));
+ mouseOverAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(1.0, KeyTime.FromTimeSpan(TimeSpan.Zero)));
+ mouseOverStoryboard.Children.Add(mouseOverAnimation);
+ mouseOverState.Storyboard = mouseOverStoryboard;
+ commonStatesGroup.States.Add(mouseOverState);
+
+ // Selection States Group
+ var selectionStatesGroup = new VisualStateGroup { Name = "SelectionStates" };
+
+ // Unselected state
+ var unselectedState = new VisualState { Name = "Unselected" };
+ selectionStatesGroup.States.Add(unselectedState);
+
+ // Selected state
+ var selectedState = new VisualState { Name = "Selected" };
+ var selectedStoryboard = new Storyboard();
+ var selectedAnimation = new DoubleAnimationUsingKeyFrames();
+ Storyboard.SetTargetName(selectedAnimation, "Border");
+ Storyboard.SetTargetProperty(selectedAnimation, new PropertyPath(Border.OpacityProperty));
+ selectedAnimation.KeyFrames.Add(new EasingDoubleKeyFrame(1.0, KeyTime.FromTimeSpan(TimeSpan.Zero)));
+ selectedStoryboard.Children.Add(selectedAnimation);
+ selectedState.Storyboard = selectedStoryboard;
+ selectionStatesGroup.States.Add(selectedState);
+
+ // SelectedUnfocused state
+ var selectedUnfocusedState = new VisualState { Name = "SelectedUnfocused" };
+ selectionStatesGroup.States.Add(selectedUnfocusedState);
+
+ // Add visual state groups
+ visualStateGroups.Add(commonStatesGroup);
+ visualStateGroups.Add(selectionStatesGroup);
+
+ // Create the template
+ var gridFactory = new FrameworkElementFactory(typeof(Grid));
+ gridFactory.SetValue(FrameworkElement.MarginProperty, grid.Margin);
+ gridFactory.SetValue(UIElement.SnapsToDevicePixelsProperty, grid.SnapsToDevicePixels);
+ gridFactory.SetValue(Panel.BackgroundProperty, grid.Background);
+
+ var contentPresenterFactory = new FrameworkElementFactory(typeof(ContentPresenter));
+ gridFactory.AppendChild(contentPresenterFactory);
+
+ var borderFactory = new FrameworkElementFactory(typeof(Border), "Border");
+ borderFactory.SetValue(Border.OpacityProperty, border.Opacity);
+ borderFactory.SetValue(Border.BorderThicknessProperty, border.BorderThickness);
+ borderFactory.SetValue(Border.BorderBrushProperty, border.BorderBrush);
+ borderFactory.SetValue(Border.BackgroundProperty, border.Background);
+ gridFactory.AppendChild(borderFactory);
+
+ template.VisualTree = gridFactory;
+ style.Setters.Add(new Setter(Control.TemplateProperty, template));
+
+ return style;
+ }
+
+ private static Style GetContextMenuStyle()
+ {
+ var contextMenuStyle = new Style(typeof(ContextMenu));
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.PlacementProperty, PlacementMode.MousePoint));
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.ForegroundProperty, _primaryCharcoal100));
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.FontSizeProperty, 13.0));
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.FontFamilyProperty, _artifaktElementRegular));
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.FontWeightProperty, FontWeights.Medium));
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.SnapsToDevicePixelsProperty, true));
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.OverridesDefaultStyleProperty, true));
+
+ var contextMenuTemplate = new ControlTemplate(typeof(ContextMenu));
+ var border = new FrameworkElementFactory(typeof(Border));
+ border.Name = "Border";
+ border.SetValue(Border.BackgroundProperty, new TemplateBindingExtension(ContextMenu.BackgroundProperty));
+ border.SetValue(Border.BorderThicknessProperty, new Thickness(0));
+
+ var stackPanel = new FrameworkElementFactory(typeof(StackPanel));
+ stackPanel.SetValue(StackPanel.MarginProperty, new Thickness(0, 10, 0, 0));
+ stackPanel.SetValue(StackPanel.ClipToBoundsProperty, true);
+ stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Vertical);
+ stackPanel.SetValue(StackPanel.IsItemsHostProperty, true);
+
+ border.AppendChild(stackPanel);
+ contextMenuTemplate.VisualTree = border;
+ contextMenuStyle.Setters.Add(new Setter(ContextMenu.TemplateProperty, contextMenuTemplate));
+
+ return contextMenuStyle;
+ }
+
+ #endregion
+
+ #region Expander Control Template
+
+ private void CreateExpanderStyle()
+ {
+ var style = new Style(typeof(Expander));
+ style.Setters.Add(new Setter(Control.TemplateProperty, CreateExpanderControlTemplate()));
+ this.Resources.Add(typeof(Expander), style);
+ }
+
+ private ControlTemplate CreateExpanderControlTemplate()
+ {
+ var template = new ControlTemplate(typeof(Expander));
+
+ // Create main Grid
+ var mainGridFactory = new FrameworkElementFactory(typeof(Grid));
+
+ // Add RowDefinitions correctly
+ var autoRow = new FrameworkElementFactory(typeof(RowDefinition));
+ autoRow.SetValue(RowDefinition.HeightProperty, GridLength.Auto);
+ mainGridFactory.AppendChild(autoRow);
+
+ var contentRow = new FrameworkElementFactory(typeof(RowDefinition));
+ contentRow.Name = "ContentRow";
+ contentRow.SetValue(RowDefinition.HeightProperty, new GridLength(0));
+ mainGridFactory.AppendChild(contentRow);
+
+ var headerBorderFactory = CreateHeaderBorder();
+ headerBorderFactory.SetValue(Grid.RowProperty, 0);
+ mainGridFactory.AppendChild(headerBorderFactory);
+
+ var contentBorderFactory = new FrameworkElementFactory(typeof(Border));
+ contentBorderFactory.Name = "Content";
+ contentBorderFactory.SetValue(Grid.RowProperty, 1);
+
+ var contentPresenterFactory = new FrameworkElementFactory(typeof(ContentPresenter));
+ contentBorderFactory.AppendChild(contentPresenterFactory);
+ mainGridFactory.AppendChild(contentBorderFactory);
+
+ template.VisualTree = mainGridFactory;
+
+ // Add triggers with correct DependencyProperty
+ var expandedTrigger = new Trigger
+ {
+ Property = Expander.IsExpandedProperty,
+ Value = true
+ };
+ expandedTrigger.Setters.Add(new Setter(RowDefinition.HeightProperty, new Binding("Height")
+ {
+ ElementName = "Content"
+ }, "ContentRow"));
+ template.Triggers.Add(expandedTrigger);
+
+ return template;
+ }
+
+ private FrameworkElementFactory CreateHeaderBorder()
+ {
+ var headerBorderFactory = new FrameworkElementFactory(typeof(Border));
+ headerBorderFactory.Name = "headerBorder";
+ headerBorderFactory.SetValue(Border.CornerRadiusProperty, _cornerRadius);
+ headerBorderFactory.SetValue(Border.PaddingProperty, new Thickness(0));
+ headerBorderFactory.SetValue(Border.MarginProperty, new Thickness(0));
+
+ // Set Background binding
+ headerBorderFactory.SetBinding(Border.BackgroundProperty, new Binding("DataContext.Background")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1),
+ Converter = _colorToSolidColorBrushConverter
+ });
+
+ // Set IsEnabled binding for ToolTipService
+ headerBorderFactory.SetValue(ToolTipService.IsEnabledProperty,
+ new Binding("IsExpanded")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
+ Converter = _inverseBooleanConverter
+ });
+
+ // Set ToolTip
+ headerBorderFactory.SetValue(Border.ToolTipProperty, CreateToolTip());
+
+ // Add the main grid for header content
+ var headerGridFactory = CreateHeaderGrid();
+ headerBorderFactory.AppendChild(headerGridFactory);
+
+ return headerBorderFactory;
+ }
+
+ private FrameworkElementFactory CreateHeaderGrid()
+ {
+ var gridFactory = new FrameworkElementFactory(typeof(Grid));
+
+ // Add column definitions directly
+ var col1 = new FrameworkElementFactory(typeof(ColumnDefinition));
+ col1.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star));
+ gridFactory.AppendChild(col1);
+
+ var col2 = new FrameworkElementFactory(typeof(ColumnDefinition));
+ col2.SetValue(ColumnDefinition.WidthProperty, GridLength.Auto);
+ gridFactory.AppendChild(col2);
+
+ var col3 = new FrameworkElementFactory(typeof(ColumnDefinition));
+ col3.SetValue(ColumnDefinition.WidthProperty, GridLength.Auto);
+ gridFactory.AppendChild(col3);
+
+ var col4 = new FrameworkElementFactory(typeof(ColumnDefinition));
+ col4.SetValue(ColumnDefinition.WidthProperty, GridLength.Auto);
+ gridFactory.AppendChild(col4);
+
+ // Add ContentPresenter
+ var contentPresenterFactory = new FrameworkElementFactory(typeof(ContentPresenter));
+ contentPresenterFactory.SetValue(Grid.ColumnSpanProperty, 3);
+ contentPresenterFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(4));
+ contentPresenterFactory.SetValue(ContentPresenter.ContentSourceProperty, "Header");
+ contentPresenterFactory.SetValue(ContentPresenter.RecognizesAccessKeyProperty, true);
+ contentPresenterFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Left);
+ gridFactory.AppendChild(contentPresenterFactory);
+
+ // Add Warning/Error Icon
+ gridFactory.AppendChild(CreateWarningErrorIcon());
+
+ // Add Frozen Button Grid
+ gridFactory.AppendChild(CreateFrozenButtonGrid());
+
+ // Add Expander Toggle Button
+ gridFactory.AppendChild(CreateExpanderToggleButton());
+
+ // Add Context Menu Button
+ gridFactory.AppendChild(CreateContextMenuButton());
+
+ return gridFactory;
+ }
+
+ private FrameworkElementFactory CreateWarningErrorIcon()
+ {
+ var imageFactory = new FrameworkElementFactory(typeof(Image));
+ imageFactory.SetValue(Grid.ColumnProperty, 0);
+ imageFactory.SetValue(FrameworkElement.WidthProperty, 16.0);
+ imageFactory.SetValue(FrameworkElement.HeightProperty, 16.0);
+ imageFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Bottom);
+ imageFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Right);
+ imageFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 0, 2.5, 2.5));
+
+ var imageStyle = new Style(typeof(Image));
+
+ // Warning trigger
+ var warningTrigger = new DataTrigger
+ {
+ Binding = new Binding("AnnotationModel.GroupState"),
+ Value = ElementState.Warning
+ };
+ warningTrigger.Setters.Add(new Setter(Image.SourceProperty, _warningImage));
+ imageStyle.Triggers.Add(warningTrigger);
+
+ // Error trigger
+ var errorTrigger = new DataTrigger
+ {
+ Binding = new Binding("AnnotationModel.GroupState"),
+ Value = ElementState.Error
+ };
+ errorTrigger.Setters.Add(new Setter(Image.SourceProperty, _errorImage));
+ imageStyle.Triggers.Add(errorTrigger);
+
+ imageFactory.SetValue(FrameworkElement.StyleProperty, imageStyle);
+
+ return imageFactory;
+ }
+
+ private FrameworkElementFactory CreateToolTip()
+ {
+ var toolTip = new FrameworkElementFactory(typeof(DynamoToolTip));
+ toolTip.SetValue(DynamoToolTip.AttachmentSideProperty, DynamoToolTip.Side.Top);
+ toolTip.SetValue(FrameworkElement.StyleProperty, _dynamoToolTipTopStyle);
+ toolTip.SetValue(FrameworkElement.MarginProperty, new Thickness(5, 0, 0, 0));
+
+ var stackPanel = new FrameworkElementFactory(typeof(StackPanel));
+ stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Vertical);
+ stackPanel.SetValue(FrameworkElement.MaxWidthProperty, 320.0);
+
+ var textBlock1 = new FrameworkElementFactory(typeof(TextBlock));
+ textBlock1.SetBinding(TextBlock.TextProperty, new Binding("AnnotationText"));
+ textBlock1.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 0, 0, 10));
+
+ var textBlock2 = new FrameworkElementFactory(typeof(TextBlock));
+ textBlock2.SetBinding(TextBlock.TextProperty, new Binding("AnnotationDescriptionText"));
+ textBlock2.SetValue(TextBlock.TextWrappingProperty, TextWrapping.WrapWithOverflow);
+
+ stackPanel.AppendChild(textBlock1);
+ stackPanel.AppendChild(textBlock2);
+ toolTip.AppendChild(stackPanel);
+
+ return toolTip;
+ }
+
+ private FrameworkElementFactory CreateFrozenButtonGrid()
+ {
+ var gridFactory = new FrameworkElementFactory(typeof(Grid));
+
+ // Create grid style with visibility trigger
+ var gridStyle = new Style(typeof(Grid));
+ gridStyle.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Visible));
+
+ var visibilityTrigger = new DataTrigger
+ {
+ Binding = new Binding("DataContext.Zoom")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(WorkspaceView), 1),
+ Converter = _zoomToVisibilityCollapsedConverter
+ },
+ Value = Visibility.Visible
+ };
+ visibilityTrigger.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Collapsed));
+ gridStyle.Triggers.Add(visibilityTrigger);
+ gridFactory.SetValue(FrameworkElement.StyleProperty, gridStyle);
+
+ // Create frozen button
+ var buttonFactory = new FrameworkElementFactory(typeof(Button));
+ buttonFactory.SetValue(FrameworkElement.WidthProperty, 16.0);
+ buttonFactory.SetValue(FrameworkElement.HeightProperty, 16.0);
+ buttonFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 0, 3.5, 3));
+ buttonFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Right);
+ buttonFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Bottom);
+ buttonFactory.SetValue(Button.CommandProperty, new Binding("ToggleIsFrozenGroupCommand"));
+ buttonFactory.SetValue(Button.TemplateProperty, CreateFrozenButtonZoomedInTemplate());
+ buttonFactory.SetBinding(UIElement.VisibilityProperty, new Binding("DataContext.AnnotationModel.IsFrozen")
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1),
+ Converter = _boolToVisibilityCollapsedConverter
+ });
+
+ // Create button tooltip
+ var toolTipFactory = new FrameworkElementFactory(typeof(ToolTip));
+ toolTipFactory.SetValue(FrameworkElement.StyleProperty, _createGenericToolTipLightStyle);
+
+ var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
+ textBlockFactory.SetValue(TextBlock.TextProperty, Wpf.Properties.Resources.GroupFrozenButtonToolTip);
+
+ toolTipFactory.AppendChild(textBlockFactory);
+ buttonFactory.SetValue(ToolTipProperty, toolTipFactory);
+
+ gridFactory.AppendChild(buttonFactory);
+ return gridFactory;
+ }
+
+ private FrameworkElementFactory CreateExpanderToggleButton()
+ {
+ var expanderTemplate = CreateGroupExpanderTemplate();
+ var toggleButtonFactory = new FrameworkElementFactory(typeof(ToggleButton));
+ toggleButtonFactory.SetValue(Grid.ColumnProperty, 2);
+ toggleButtonFactory.SetValue(FrameworkElement.OverridesDefaultStyleProperty, true);
+ toggleButtonFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 0, 0, 2.5));
+ toggleButtonFactory.SetValue(Control.TemplateProperty, expanderTemplate);
+ toggleButtonFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Bottom);
+
+ toggleButtonFactory.SetBinding(ToggleButton.IsCheckedProperty, new Binding("IsExpanded")
+ {
+ Mode = BindingMode.TwoWay,
+ RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent)
+ });
+
+ return toggleButtonFactory;
+ }
+
+ private FrameworkElementFactory CreateContextMenuButton()
+ {
+ var buttonFactory = new FrameworkElementFactory(typeof(Button));
+ buttonFactory.Name = "contextMenu";
+ buttonFactory.SetValue(Grid.ColumnProperty, 3);
+ buttonFactory.SetValue(Button.BackgroundProperty, Brushes.Transparent);
+ buttonFactory.SetValue(Button.BorderBrushProperty, Brushes.Transparent);
+ buttonFactory.SetValue(Button.BorderThicknessProperty, new Thickness(0));
+ buttonFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 2.5, 0, 2.5));
+ buttonFactory.SetValue(FrameworkElement.HeightProperty, 16.0);
+ buttonFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Bottom);
+ buttonFactory.AddHandler(Button.ClickEvent, new RoutedEventHandler(contextMenu_Click));
+
+ // Create button style
+ var buttonStyle = new Style(typeof(Button));
+ buttonStyle.Setters.Add(new Setter(Button.OverridesDefaultStyleProperty, true));
+
+ // Create button template
+ var buttonTemplate = new ControlTemplate(typeof(Button));
+ var borderFactory = new FrameworkElementFactory(typeof(Border));
+ borderFactory.Name = "border";
+ borderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(0));
+ borderFactory.SetValue(Border.BackgroundProperty, new Binding()
+ {
+ RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
+ Path = new PropertyPath(Button.BackgroundProperty)
+ });
+
+ var imageFactory = new FrameworkElementFactory(typeof(Image));
+ imageFactory.Name = "menuIcon";
+ imageFactory.SetValue(FrameworkElement.WidthProperty, 16.0);
+ imageFactory.SetValue(FrameworkElement.HeightProperty, 16.0);
+ borderFactory.AppendChild(imageFactory);
+
+ buttonTemplate.VisualTree = borderFactory;
+
+ // Add triggers for menu icon
+ var notHoverDarkTrigger = new MultiDataTrigger();
+ notHoverDarkTrigger.Conditions.Add(new Condition(new Binding("IsMouseOver") { RelativeSource = new RelativeSource(RelativeSourceMode.Self) }, false));
+ notHoverDarkTrigger.Conditions.Add(new Condition(new Binding("Background") { Converter = _backgroundConditionEvaluator }, false));
+ notHoverDarkTrigger.Setters.Add(new Setter(Image.SourceProperty, _menuGreyImage, "menuIcon"));
+
+ var notHoverLightTrigger = new MultiDataTrigger();
+ notHoverLightTrigger.Conditions.Add(new Condition(new Binding("IsMouseOver") { RelativeSource = new RelativeSource(RelativeSourceMode.Self) }, false));
+ notHoverLightTrigger.Conditions.Add(new Condition(new Binding("Background") { Converter = _backgroundConditionEvaluator }, true));
+ notHoverLightTrigger.Setters.Add(new Setter(Image.SourceProperty, _menuWhiteImage, "menuIcon"));
+
+ var hoverTrigger = new MultiDataTrigger();
+ hoverTrigger.Conditions.Add(new Condition(new Binding("IsMouseOver") { RelativeSource = new RelativeSource(RelativeSourceMode.Self) }, true));
+ hoverTrigger.Setters.Add(new Setter(Image.SourceProperty, _menuHoverImage, "menuIcon"));
+
+ buttonTemplate.Triggers.Add(notHoverDarkTrigger);
+ buttonTemplate.Triggers.Add(notHoverLightTrigger);
+ buttonTemplate.Triggers.Add(hoverTrigger);
+
+ buttonStyle.Setters.Add(new Setter(Button.TemplateProperty, buttonTemplate));
+ buttonFactory.SetValue(FrameworkElement.StyleProperty, buttonStyle);
+
+ return buttonFactory;
+ }
+
+ #endregion
+
+ private void SetModelAreaHeight()
+ {
+ // We only want to change the ModelAreaHeight
+ // if the collapsedAnnotationRectangle is visible,
+ // as if its not it will be equal to the height of the
+ // contained nodes + the TextBlockHeight
+ if (ViewModel is null || !this.collapsedAnnotationRectangle.IsVisible) return;
+ ViewModel.ModelAreaHeight = this.collapsedAnnotationRectangle.ActualHeight;
+ ViewModel.AnnotationModel.UpdateBoundaryFromSelection();
+ }
+
+ //Set the max width of text area based on the width of the longest word in the text
+ private void SetTextMaxWidth()
+ {
+ var words = this.ViewModel.AnnotationText.Split(' ');
+ var maxLength = 0;
+ string longestWord = words[0];
+
+ foreach (var w in words)
+ {
+ if (w.Length > maxLength)
+ {
+ longestWord = w;
+ maxLength = w.Length;
+ }
+ }
+
+ var formattedText = new FormattedText(
+ longestWord,
+ System.Globalization.CultureInfo.CurrentUICulture,
+ FlowDirection.LeftToRight,
+ new Typeface(this.groupTextBlock.FontFamily, this.groupTextBlock.FontStyle, this.groupTextBlock.FontWeight, this.groupTextBlock.FontStretch),
+ this.groupTextBlock.FontSize,
+ Brushes.Black);
+
+ var margin = this.textBlockGrid.Margin.Right + this.textBlockGrid.Margin.Left;
+
+ this.ViewModel.AnnotationModel.Width = formattedText.Width + margin;
+ this.ViewModel.AnnotationModel.TextMaxWidth = formattedText.Width + margin;
+ }
+
+ private void SetTextHeight()
+ {
+ if (this.groupDescriptionTextBlock is null || this.groupTextBlock is null || ViewModel is null)
+ {
+ return;
+ }
+
+ // Use the DesiredSize and not the Actual height. Because when Textblock is collapsed,
+ // Actual height is same as previous size.
+ ViewModel.AnnotationModel.TextBlockHeight =
+ this.groupDescriptionControls.DesiredSize.Height +
+ this.groupNameControl.DesiredSize.Height;
+ }
+
+ private (double maxWidth, double totalHeight) MeasurePortBounds(ItemsControl portControl)
+ {
+ double maxWidth = 0;
+ double totalHeight = 0;
+
+ foreach (var item in portControl.Items)
+ {
+ if (portControl.ItemContainerGenerator.ContainerFromItem(item) is FrameworkElement container && container.IsVisible)
+ {
+ container.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+ var size = container.DesiredSize;
+
+ maxWidth = Math.Max(maxWidth, size.Width);
+ totalHeight += size.Height;
+ }
+ }
+
+ return (maxWidth, totalHeight);
+ }
}
}
diff --git a/test/DynamoCoreWpfTests/AnnotationViewTests.cs b/test/DynamoCoreWpfTests/AnnotationViewTests.cs
index 7778f01810d..5e03b909cc8 100644
--- a/test/DynamoCoreWpfTests/AnnotationViewTests.cs
+++ b/test/DynamoCoreWpfTests/AnnotationViewTests.cs
@@ -103,7 +103,10 @@ public void UngroupingCollapsedGroupWillUnCollapseAllGroupContent()
Assert.That(groupContent.All(x => x.IsCollapsed == true));
// Act
- annotationView.UngroupAnnotation.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent));
+ var ungrp = annotationView.AnnotationGrid.ContextMenu.Items
+ .OfType()
+ .FirstOrDefault(x => x.Name == "UngroupAnnotation");
+ ungrp.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent));
// Assert
Assert.That(groupContent.All(x => x.IsCollapsed == false));
diff --git a/test/DynamoCoreWpfTests/GroupStylesTests.cs b/test/DynamoCoreWpfTests/GroupStylesTests.cs
index bf32ccfa21a..9bd319d7061 100644
--- a/test/DynamoCoreWpfTests/GroupStylesTests.cs
+++ b/test/DynamoCoreWpfTests/GroupStylesTests.cs
@@ -108,7 +108,7 @@ public void TestAddGroupStyle_ContextMenu()
preferencesWindow.CloseButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
DispatcherUtil.DoEvents();
- var groupStylesMenuItem = annotationView.AnnotationContextMenu
+ var groupStylesMenuItem = annotationView.AnnotationGrid.ContextMenu
.Items
.OfType()
.First(x => x.Header.ToString() == "Group Style");