using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using InABox.Core; using Microsoft.Xaml.Behaviors; using Brush = System.Windows.Media.Brush; using FontStyle = System.Windows.FontStyle; using Image = System.Windows.Controls.Image; namespace InABox.WPF { public static class WPFUtils { public static T? FindLogicalParent(this DependencyObject dependencyObject) where T : DependencyObject { DependencyObject? parent = dependencyObject; do { parent = LogicalTreeHelper.GetParent(parent); } while(parent != null && parent is not T); return parent as T; } public static int GetRow(this Grid grid, DependencyObject dependencyObject) { while (true) { var parent = LogicalTreeHelper.GetParent(dependencyObject); if (parent == null) return -1; if (parent == grid) return Grid.GetRow(dependencyObject as UIElement); dependencyObject = parent; } } public static int GetRowSpan(this Grid grid, DependencyObject dependencyObject) { while (true) { var parent = LogicalTreeHelper.GetParent(dependencyObject); if (parent == null) return -1; if (parent == grid) return Grid.GetRowSpan(dependencyObject as UIElement); dependencyObject = parent; } } public static int GetColumn(this Grid grid, DependencyObject dependencyObject) { while (true) { var parent = LogicalTreeHelper.GetParent(dependencyObject); if (parent == null) return -1; if (parent == grid) return Grid.GetColumn(dependencyObject as UIElement); dependencyObject = parent; } } public static int GetColumnSpan(this Grid grid, DependencyObject dependencyObject) { while (true) { var parent = LogicalTreeHelper.GetParent(dependencyObject); if (parent == null) return -1; if (parent == grid) return Grid.GetColumnSpan(dependencyObject as UIElement); dependencyObject = parent; } } public static void SetGridPosition(this FrameworkElement element, int row, int column, int rowspan = 1, int colspan = 1) { element.SetValue(Grid.ColumnProperty, column); element.SetValue(Grid.ColumnSpanProperty, Math.Max(1, colspan)); element.SetValue(Grid.RowProperty, row); element.SetValue(Grid.RowSpanProperty, Math.Max(1, rowspan)); } public static IEnumerable FindVisualChildren(this DependencyObject depObj) { if (depObj != null) //ContentControl cc = depObj as ContentControl; //if (cc != null) //{ // if (cc.Content == null) // yield return null; // if (cc.Content is T) // yield return cc.Content as T; // foreach (var child in FindVisualChildren(cc.Content as DependencyObject)) // yield return child; //} //else for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) { var child = VisualTreeHelper.GetChild(depObj, i); if (child is null) continue; if (child is T t) yield return t; foreach (var childOfChild in FindVisualChildren(child)) yield return childOfChild; } } #region Menu Utils private static void ItemsControlInsert(ItemsControl menu, FrameworkElement item, int index) { if (index != -1) { menu.Items.Insert(index, item); } else { menu.Items.Add(item); } } private static MenuItem DoAddMenuItem(ItemsControl menu, string caption, Bitmap? image, bool enabled, int index = -1) { var item = new MenuItem { Header = caption, IsEnabled = enabled }; if (image != null) item.Icon = new Image() { Source = enabled ? image.AsBitmapImage(24, 24) : image.AsGrayScale().AsBitmapImage(24, 24) }; ItemsControlInsert(menu, item, index); return item; } private static MenuItem DoAddMenuItem(ItemsControl menu, string caption, Bitmap? image, Action? click, bool enabled, int index = -1) { var item = DoAddMenuItem(menu, caption, image, enabled, index); if (click != null) { item.Click += (o, e) => { click(); }; } return item; } private static MenuItem DoAddMenuItem(ItemsControl menu, string caption, Bitmap? image, T tag, Action click, bool enabled, int index = -1) { var item = DoAddMenuItem(menu, caption, image, enabled, index); item.Tag = tag; item.Click += (o, e) => { click((T)(o as MenuItem)!.Tag); }; return item; } public delegate void CheckToggleAction(T tag, bool isChecked); private static MenuItem DoAddCheckItem(ItemsControl menu, string caption, T tag, CheckToggleAction click, bool isChecked, bool enabled, int index = -1) { var item = new MenuItem { Header = caption, IsEnabled = enabled, IsCheckable = true, IsChecked = isChecked }; item.Tag = tag; item.Click += (o, e) => { click((T)(o as MenuItem)!.Tag, item.IsChecked); }; ItemsControlInsert(menu, item, index); return item; } private static Separator DoAddSeparator(ItemsControl menu, int index) { var separator = new Separator(); ItemsControlInsert(menu, separator, index); return separator; } private static Separator DoAddSeparatorIfNeeded(ItemsControl menu, int index) { if (menu.Items.Count == 0) return null; var lastIndex = index != -1 ? index - 1 : menu.Items.Count - 1; if (lastIndex < 0 || lastIndex >= menu.Items.Count) return null; var last = menu.Items[lastIndex]; if (last is Separator) return null; var separator = new Separator(); ItemsControlInsert(menu, separator, index); return separator; } private static void DoRemoveUnnecessarySeparators(ItemsControl menu) { while(menu.Items.Count > 0 && menu.Items[0] is Separator) { menu.Items.RemoveAt(0); } while(menu.Items.Count > 0 && menu.Items[^1] is Separator) { menu.Items.RemoveAt(menu.Items.Count - 1); } } public static Separator AddSeparator(this ContextMenu menu, int index = -1) => DoAddSeparator(menu, index); public static Separator AddSeparator(this MenuItem menu, int index = -1) => DoAddSeparator(menu, index); public static Separator? AddSeparatorIfNeeded(this ContextMenu menu, int index = -1) => DoAddSeparatorIfNeeded(menu, index); public static Separator? AddSeparatorIfNeeded(this MenuItem menu, int index = -1) => DoAddSeparatorIfNeeded(menu, index); public static void RemoveUnnecessarySeparators(this ContextMenu menu) => DoRemoveUnnecessarySeparators(menu); public static void RemoveUnnecessarySeparators(this MenuItem menu) => DoRemoveUnnecessarySeparators(menu); public static MenuItem AddItem(this ContextMenu menu, string caption, Bitmap? image, Action? click, bool enabled = true, int index = -1) => DoAddMenuItem(menu, caption, image, click, enabled, index); public static MenuItem AddItem(this MenuItem menu, string caption, Bitmap? image, Action? click, bool enabled = true, int index = -1) => DoAddMenuItem(menu, caption, image, click, enabled, index); public static MenuItem AddItem(this ContextMenu menu, string caption, Bitmap? image, T tag, Action click, bool enabled = true, int index = -1) => DoAddMenuItem(menu, caption, image, tag, click, enabled, index); public static MenuItem AddItem(this MenuItem menu, string caption, Bitmap? image, T tag, Action click, bool enabled = true, int index = -1) => DoAddMenuItem(menu, caption, image, tag, click, enabled, index); public static MenuItem AddCheckItem(this ContextMenu menu, string caption, T tag, CheckToggleAction click, bool isChecked = false, bool enabled = true, int index = -1) => DoAddCheckItem(menu, caption, tag, click, isChecked, enabled, index); #endregion } public class TextBoxPlaceholderBehaviour : Behavior { public static readonly DependencyProperty TextProperty = DependencyProperty.Register( nameof(Text), typeof(String), typeof(TextBoxPlaceholderBehaviour), new PropertyMetadata("")); public String Text { get => (String)GetValue(TextProperty); set { SetValue(TextProperty, value); UpdateAssociatedObject(); } } public static readonly DependencyProperty TextColorProperty = DependencyProperty.Register( nameof(TextColor), typeof(System.Windows.Media.Color), typeof(TextBoxPlaceholderBehaviour), new PropertyMetadata(Colors.Gray)); public System.Windows.Media.Color TextColor { get => (System.Windows.Media.Color)GetValue(TextColorProperty); set { SetValue(TextColorProperty, value); UpdateAssociatedObject(); } } private readonly PropertyDescriptor _propertyDescriptor = DependencyPropertyDescriptor.FromProperty(TextBox.BackgroundProperty, typeof(TextBox)); private Brush? _originalBackground; private Brush? _placeholderBackground; protected override void OnAttached() { base.OnAttached(); // Store the Background that has been set on the Text Box (usu through XAML) _originalBackground = AssociatedObject.Background; // Start Monitoring changes to the Associated object Background property _propertyDescriptor.AddValueChanged(AssociatedObject, OnBackgroundChanged); AssociatedObject.TextChanged += OnTextChanged; AssociatedObject.SizeChanged += OnSizeChanged; UpdateAssociatedObject(); } protected override void OnDetaching() { AssociatedObject.SizeChanged -= OnSizeChanged; AssociatedObject.TextChanged -= OnTextChanged; _propertyDescriptor?.RemoveValueChanged(AssociatedObject,OnBackgroundChanged); AssociatedObject.Background = _originalBackground; base.OnDetaching(); } private void OnBackgroundChanged(object? sender, EventArgs e) { // Update the Saved Background (usu a color, but might not be?) _originalBackground = AssociatedObject.Background; _placeholderBackground = null; UpdateAssociatedObject(); } private void OnTextChanged(object sender, TextChangedEventArgs e) { UpdateAssociatedObject(); } private void SetBackground(Brush? brush) { _propertyDescriptor.RemoveValueChanged(AssociatedObject, OnBackgroundChanged); AssociatedObject.Background = brush; _propertyDescriptor.AddValueChanged(AssociatedObject, OnBackgroundChanged); } private void OnSizeChanged(object sender, SizeChangedEventArgs e) { _placeholderBackground = null; UpdateAssociatedObject(); } private Brush CreatePlaceholder() { return new VisualBrush() { Visual = new Label() { Content = Text, Margin = new Thickness(0), Padding = new Thickness(4, 0, 2, 0), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, HorizontalContentAlignment = AssociatedObject.HorizontalContentAlignment, VerticalContentAlignment = AssociatedObject.VerticalContentAlignment, FontFamily = AssociatedObject.FontFamily, FontStretch = AssociatedObject.FontStretch, FontSize = AssociatedObject.FontSize, FontWeight = AssociatedObject.FontWeight, FontStyle = FontStyles.Italic, Background = _originalBackground?.Clone(), Foreground = new SolidColorBrush(TextColor), Width = AssociatedObject.ActualWidth, Height = AssociatedObject.ActualHeight }, Stretch = Stretch.None, TileMode = TileMode.None, AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Center }; } private void UpdateAssociatedObject() { if (String.IsNullOrWhiteSpace(AssociatedObject.Text) && _placeholderBackground == null) { _placeholderBackground = CreatePlaceholder(); SetBackground(_placeholderBackground); } else if (!String.IsNullOrWhiteSpace(AssociatedObject.Text) && _placeholderBackground != null) { _placeholderBackground = null; SetBackground(_originalBackground); } } } // public class TextBoxPlaceholderBehaviour : Behavior // { // // public static readonly DependencyProperty TextProperty = // DependencyProperty.Register( // nameof(Text), // typeof(String), // typeof(TextBoxPlaceholderBehaviour), // new PropertyMetadata("")); // // // public String Text // { // get => (String)GetValue(TextProperty); // set => SetValue(TextProperty, value); // } // // protected override void OnAttached() // { // base.OnAttached(); // AssociatedObject.GotFocus += AssociatedObjectOnGotFocus; // AssociatedObject.LostFocus += AssociatedObjectOnLostFocus; // } // // protected override void OnDetaching() // { // AssociatedObject.GotFocus -= AssociatedObjectOnGotFocus; // AssociatedObject.LostFocus -= AssociatedObjectOnLostFocus; // base.OnDetaching(); // } // // private void AssociatedObjectOnGotFocus(object sender, RoutedEventArgs e) // { // if (String.Equals(AssociatedObject.Text, Text)) // { // AssociatedObject.Text = ""; // AssociatedObject.Foreground = new SolidColorBrush(Colors.Black); // } // } // // private void AssociatedObjectOnLostFocus(object sender, RoutedEventArgs e) // { // if (String.IsNullOrWhiteSpace(AssociatedObject.Text)) // { // AssociatedObject.Text = Text; // AssociatedObject.Foreground = new SolidColorBrush(Colors.Gray); // } // } // } public class TextBoxEnterAsTabBehavior : Behavior { protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown; } protected override void OnDetaching() { AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown; base.OnDetaching(); } private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs args) { if (args.Key != Key.Enter) { return; } args.Handled = true; AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); } } }