整理一下项目文件

This commit is contained in:
艾竹
2022-10-28 22:45:39 +08:00
parent 334297b074
commit 513937c1d6
598 changed files with 684 additions and 544 deletions

View File

@@ -0,0 +1,95 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// 包含一些常用的动画辅助方法
/// </summary>
public class AnimationHelper
{
/// <summary>
/// 创建一个Thickness动画
/// </summary>
/// <param name="thickness"></param>
/// <param name="milliseconds"></param>
/// <returns></returns>
public static ThicknessAnimation CreateAnimation(Thickness thickness = default, double milliseconds = 200)
{
return new ThicknessAnimation(thickness, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
{
EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
};
}
/// <summary>
/// 创建一个Double动画
/// </summary>
/// <param name="toValue"></param>
/// <param name="milliseconds"></param>
/// <returns></returns>
public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200)
{
return new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
{
EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
};
}
public const string DigitsPattern = @"[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?";
internal static void DecomposeGeometryStr(string geometryStr, out double[] arr)
{
var collection = Regex.Matches(geometryStr, DigitsPattern);
arr = new double[collection.Count];
for (var i = 0; i < collection.Count; i++)
{
arr[i] = Convert.ToDouble(collection[i].Value);
}
}
internal static Geometry ComposeGeometry(string[] strings, double[] arr)
{
var builder = new StringBuilder(strings[0]);
for (var i = 0; i < arr.Length; i++)
{
var s = strings[i + 1];
var n = arr[i];
if (!double.IsNaN(n))
{
builder.Append(n).Append(s);
}
}
return Geometry.Parse(builder.ToString());
}
internal static Geometry InterpolateGeometry(double[] from, double[] to, double progress, string[] strings)
{
var accumulated = new double[to.Length];
for (var i = 0; i < to.Length; i++)
{
var fromValue = from[i];
accumulated[i] = fromValue + (to[i] - fromValue) * progress;
}
return ComposeGeometry(strings, accumulated);
}
internal static double[] InterpolateGeometryValue(double[] from, double[] to, double progress)
{
var accumulated = new double[to.Length];
for (var i = 0; i < to.Length; i++)
{
var fromValue = from[i];
accumulated[i] = fromValue + (to[i] - fromValue) * progress;
}
return accumulated;
}
}
}

View File

@@ -0,0 +1,23 @@
<UserControl x:Class="AIStudio.Wpf.DiagramHelper.Controls.Barcode"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:AIStudio.Wpf.DiagramHelper.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Path Name="imageBarcodeEncoderGeometry" Grid.RowSpan="3" Grid.ColumnSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="Black" Stretch="Fill" Width="Auto" Height="Auto" />
<ContentControl x:Name="PART_Icon" Grid.Row="1" Grid.Column="1"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WpfAnimatedGif;
using ZXing;
using ZXing.Presentation;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// Barcode.xaml 的交互逻辑
/// </summary>
public partial class Barcode : UserControl
{
public Barcode()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(Barcode), new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender, OnFormattedTextInvalidated));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty FormatProperty = DependencyProperty.Register(
"Format", typeof(BarcodeFormat), typeof(Barcode), new FrameworkPropertyMetadata(
BarcodeFormat.QR_CODE,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender, OnFormattedTextInvalidated));
public BarcodeFormat Format
{
get => (BarcodeFormat)GetValue(FormatProperty);
set => SetValue(FormatProperty, value);
}
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register(
"Size", typeof(double), typeof(Barcode), new FrameworkPropertyMetadata(
512d,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender, OnFormattedTextInvalidated));
public double Size
{
get => (double)GetValue(SizeProperty);
set => SetValue(SizeProperty, value);
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
"Icon", typeof(string), typeof(Barcode), new FrameworkPropertyMetadata(null, OnIconChanged));
public string Icon
{
get => (string)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var barcode = (Barcode)d;
barcode.OnIconChanged();
}
private void OnIconChanged()
{
if (!string.IsNullOrEmpty(this.Icon))
{
var suffix = System.IO.Path.GetExtension(this.Icon).ToLower();
Image image = new Image() { Stretch = Stretch.UniformToFill };
var icon = new BitmapImage(new Uri(Icon));
if (suffix != ".gif")
{
image.Source = icon;
}
else
{
image.SetCurrentValue(ImageBehavior.AnimatedSourceProperty, icon);
image.SetCurrentValue(ImageBehavior.AutoStartProperty, true);
}
PART_Icon.Content = image;
}
else
{
PART_Icon.Content = null;
}
}
private static void OnFormattedTextInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var barcode = (Barcode)d;
barcode.OnFormattedTextInvalidated();
}
private void OnFormattedTextInvalidated()
{
var writer = new BarcodeWriterGeometry
{
Format = Format,
Options = new ZXing.Common.EncodingOptions
{
Height = (int)this.Size,
Width = (int)this.Size,
Margin = 0
}
};
var image = writer.Write(Text ?? "AIStudio画板");
imageBarcodeEncoderGeometry.Data = image;
}
}
}

View File

@@ -0,0 +1,139 @@
<UserControl x:Class="AIStudio.Wpf.DiagramHelper.Controls.GradientStopControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:AIStudio.Wpf.DiagramHelper.Controls"
xmlns:Fluent="urn:fluent-ribbon"
xmlns:converter="clr-namespace:AIStudio.Wpf.DiagramHelper.Converters"
xmlns:helper="clr-namespace:AIStudio.Wpf.DiagramHelper.Helpers"
xmlns:dd="https://astudio.github.io/diagram"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ControlTemplate x:Key="RibbonDropDownButtonControlTemplate1" TargetType="{x:Type Fluent:DropDownButton}">
<Grid>
<Border x:Name="PART_ButtonBorder" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" Height="Auto">
<StackPanel x:Name="stackPanel" Margin="{TemplateBinding Padding}" Width="Auto" Orientation="Vertical">
<ContentPresenter x:Name="iconImage" Content="{Binding Icon, RelativeSource={RelativeSource TemplatedParent}}" Width="Auto" SnapsToDevicePixels="True" />
</StackPanel>
</Border>
<Popup x:Name="PART_Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Margin="0,0,-4,0" VerticalAlignment="Top" Grid.ColumnSpan="2">
<Grid Name="DropDown" SnapsToDevicePixels="True" Margin="0" VerticalAlignment="Top">
<Border x:Name="PART_DropDownBorder" MinWidth="{TemplateBinding ActualWidth}" SnapsToDevicePixels="True" Background="{DynamicResource Fluent.Ribbon.Brushes.DropDown.BackgroundBrush}" BorderBrush="{DynamicResource Fluent.Ribbon.Brushes.DropDown.BorderBrush}" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Grid Width="Auto" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Border VerticalAlignment="Top" Width="Auto" Height="Auto" BorderBrush="{DynamicResource Fluent.Ribbon.Brushes.DropDown.BackgroundBrush}" BorderThickness="1" />
<Grid Margin="1" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer x:Name="PART_ScrollViewer" Style="{DynamicResource MenuScrollViewer}" IsTabStop="False" Margin="1" MaxHeight="{TemplateBinding MaxDropDownHeight}" SnapsToDevicePixels="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden">
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Local" />
</ScrollViewer>
<Border x:Name="resizeBorder" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="11" BorderBrush="{DynamicResource Fluent.Ribbon.Brushes.DropDown.Resize.BorderBrush}" BorderThickness="0,1,0,0" Grid.Row="1" Background="{DynamicResource Fluent.Ribbon.Brushes.DropDown.Resize.BackgroundBrush}">
<Grid>
<Thumb x:Name="PART_ResizeVerticalThumb" HorizontalAlignment="Stretch" Margin="0,0,0,-90" Width="Auto" Height="10" VerticalAlignment="Top" Cursor="SizeNS" Template="{DynamicResource ResizeVerticalThumbControlTemplate}" />
<Thumb x:Name="PART_ResizeBothThumb" HorizontalAlignment="Right" Margin="0,0,0,-90" Width="10" Height="10" VerticalAlignment="Top" Cursor="SizeNWSE" Template="{DynamicResource ResizeBothThumbControlTemplate}" />
</Grid>
</Border>
</Grid>
</Grid>
</Border>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode" Value="None">
<Setter Property="Visibility" TargetName="resizeBorder" Value="Collapsed" />
</Trigger>
<Trigger Property="ResizeMode" Value="Both">
<Setter Property="Visibility" TargetName="PART_ResizeVerticalThumb" Value="Collapsed" />
<Setter Property="Visibility" TargetName="PART_ResizeBothThumb" Value="Visible" />
<Setter Property="Visibility" TargetName="resizeBorder" Value="Visible" />
</Trigger>
<Trigger Property="ResizeMode" Value="Vertical">
<Setter Property="Visibility" TargetName="PART_ResizeBothThumb" Value="Collapsed" />
<Setter Property="Visibility" TargetName="PART_ResizeVerticalThumb" Value="Visible" />
<Setter Property="Visibility" TargetName="resizeBorder" Value="Visible" />
</Trigger>
<Trigger Property="IsDropDownOpen" Value="True">
<Setter Property="BorderBrush" TargetName="PART_ButtonBorder" Value="{DynamicResource Fluent.Ribbon.Brushes.Button.Pressed.BorderBrush}" />
<Setter Property="Background" TargetName="PART_ButtonBorder" Value="{DynamicResource Fluent.Ribbon.Brushes.Button.Pressed.Background}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="iconImage" Value="0.5" />
<Setter Property="Effect" TargetName="iconImage">
<Setter.Value>
<Fluent:GrayscaleEffect />
</Setter.Value>
</Setter>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsDropDownOpen" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="PART_ButtonBorder" Value="{DynamicResource Fluent.Ribbon.Brushes.Button.MouseOver.Background}" />
<Setter Property="BorderBrush" TargetName="PART_ButtonBorder" Value="{DynamicResource Fluent.Ribbon.Brushes.Button.MouseOver.BorderBrush}" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<StackPanel>
<DockPanel>
<Fluent:Button DockPanel.Dock="Right" Size="Small"
Icon="{iconPacks:Material Kind=Minus}" Command="{Binding DeleteGradientStopCommand}" Margin="5" />
<Fluent:Button DockPanel.Dock="Right" Size="Small"
Icon="{iconPacks:Material Kind=Plus}" Command="{Binding AddGradientStopCommand}" Margin="5" />
<TextBlock Text="渐变光圈" VerticalAlignment="Center" Margin="5"/>
</DockPanel>
<Grid Margin="5">
<Rectangle Fill="{Binding GradientStop, Converter={StaticResource ColorBrushConverter}}" Height="4.0" VerticalAlignment="Center" />
<ItemsControl ItemsSource="{Binding GradientStop}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid >
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Slider Maximum="1" Minimum="0" LargeChange="0.1" SmallChange="0.01"
Value="{Binding Offset}"
Foreground="{Binding Color, Converter={StaticResource ColorBrushConverter}}"
Style="{StaticResource TransparentSlider}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<DockPanel>
<!-- The following code shows theme colors mode for color gallery -->
<Fluent:DropDownButton DockPanel.Dock="Right" Margin="5" Height="24" Width="60"
Template="{StaticResource RibbonDropDownButtonControlTemplate1}"
MaxDropDownHeight="500">
<Fluent:DropDownButton.Icon>
<Grid>
<Rectangle Height="22" StrokeThickness="1" Stroke="{DynamicResource Fluent.Ribbon.Brushes.AccentBaseColorBrush}">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding SelectedGradientStop.Color}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</Fluent:DropDownButton.Icon>
<Fluent:ColorGallery SelectedColor="{Binding SelectedGradientStop.Color, Mode=TwoWay}"
Mode="StandardColors"
StandardColorGridRows="3"
Columns="10"
ThemeColorGridRows="5"
IsNoColorButtonVisible="True" />
</Fluent:DropDownButton>
<TextBlock Margin="5" VerticalAlignment="Center" Text="颜色"/>
</DockPanel>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AIStudio.Wpf.DiagramDesigner;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// GradientStopControl.xaml 的交互逻辑
/// </summary>
public partial class GradientStopControl : UserControl
{
public GradientStopControl()
{
InitializeComponent();
}
public ColorObject ColorObject
{
get { return this.DataContext as ColorObject; }
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
var element = (e.OriginalSource as FrameworkElement);
if (element.DataContext is AIStudio.Wpf.DiagramDesigner.GradientStop target)
{
ColorObject.SelectedGradientStop = target;
}
}
}
}

View File

@@ -0,0 +1,99 @@
<UserControl x:Class="AIStudio.Wpf.DiagramHelper.Controls.MultiSelectComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ComboBox Background="Transparent" BorderBrush="Transparent"
x:Name="MultiSelectCombo"
SnapsToDevicePixels="True"
OverridesDefaultStyle="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"
IsSynchronizedWithCurrentItem="True"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
Click="CheckBox_Click" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Template>
<ControlTemplate TargetType="ComboBox">
<Grid >
<ToggleButton x:Name="ToggleButton" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
Grid.Column="2" IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
Focusable="false"
ClickMode="Press" HorizontalContentAlignment="Left" >
<ToggleButton.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12"/>
</Grid.ColumnDefinitions>
<Border
x:Name="Border"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" />
<Border
x:Name="BorderComp"
Grid.Column="0"
CornerRadius="2"
Margin="1"
BorderThickness="0,0,0,0" >
<TextBlock Text="{Binding Path=Text,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" Padding="3" />
</Border>
<Border x:Name="ArrowBorder" Grid.Column="1" >
<Path
x:Name="Arrow"
Stretch="Fill" Width="5" Height="3"
Fill="Black"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 0 L 4 4 L 8 0 Z"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ArrowBorder" Property="Background" Value="{DynamicResource Fluent.Ribbon.Brushes.Button.MouseOver.Background}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
<Popup x:Name="Popup"
Placement="Bottom"
AllowsTransparency="True"
Focusable="False" IsOpen="{TemplateBinding IsDropDownOpen}"
PopupAnimation="Slide">
<Grid x:Name="DropDown"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border x:Name="DropDownBorder"
BorderThickness="1" Background="{DynamicResource WhiteBrush}"
BorderBrush="{DynamicResource GrayBrush8}"/>
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True" DataContext="{Binding}">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
</Trigger>
<Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
<Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
</Trigger>
<Trigger Property="IsDropDownOpen" Value="true">
<Setter TargetName="ToggleButton" Property="Background" Value="{DynamicResource Fluent.Ribbon.Brushes.Button.MouseOver.Background}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ComboBox.Template>
</ComboBox>
</UserControl>

View File

@@ -0,0 +1,334 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Collections;
using AIStudio.Wpf.DiagramHelper.Helpers;
using AIStudio.Wpf.DiagramDesigner;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// Interaction logic for MultiSelectComboBox.xaml
/// </summary>
public partial class MultiSelectComboBox : UserControl
{
private ObservableCollection<Node> _nodeList;
public MultiSelectComboBox()
{
InitializeComponent();
_nodeList = new ObservableCollection<Node>();
}
#region Dependency Properties
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IList), typeof(MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnItemsSourceChanged)));
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnSelectedItemsChanged)));
public static readonly DependencyProperty SelectedValuesProperty =
DependencyProperty.Register("SelectedValues", typeof(IList), typeof(MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnSelectedValuesChanged)));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MultiSelectComboBox), new UIPropertyMetadata(string.Empty));
public static readonly DependencyProperty DefaultTextProperty =
DependencyProperty.Register("DefaultText", typeof(string), typeof(MultiSelectComboBox), new UIPropertyMetadata(string.Empty));
public string DisplayMemberPath { get; set; }
public string SelectedValuePath { get; set; }
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set
{
SetValue(SelectedItemsProperty, value);
}
}
public IList SelectedValues
{
get { return (IList)GetValue(SelectedValuesProperty); }
set
{
SetValue(SelectedValuesProperty, value);
}
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string DefaultText
{
get { return (string)GetValue(DefaultTextProperty); }
set { SetValue(DefaultTextProperty, value); }
}
#endregion
#region Events
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
control.DisplayInControl();
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
control.SelectNodes();
control.SetText();
}
private static void OnSelectedValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox control = (MultiSelectComboBox)d;
control.SelectNodes();
control.SetText();
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox clickedBox = (CheckBox)sender;
if (clickedBox.Content.ToString() == "All")
{
if (clickedBox.IsChecked.Value)
{
foreach (Node node in _nodeList)
{
node.IsSelected = true;
}
}
else
{
foreach (Node node in _nodeList)
{
node.IsSelected = false;
}
}
}
else
{
int _selectedCount = 0;
foreach (Node s in _nodeList)
{
if (s.IsSelected && s.Object.ToString() != "All")
_selectedCount++;
}
if (_selectedCount == _nodeList.Count - 1)
_nodeList.FirstOrDefault(i => i.Object.ToString() == "All").IsSelected = true;
else
_nodeList.FirstOrDefault(i => i.Object.ToString() == "All").IsSelected = false;
}
SetSelectedItems();
SetText();
}
#endregion
#region Methods
private void SelectNodes()
{
if (SelectedItems != null)
{
foreach (var item in SelectedItems)
{
Node node = _nodeList.FirstOrDefault(i => i.Object == item);
if (node != null)
node.IsSelected = true;
}
}
else if (SelectedValues != null)
{
foreach (var item in SelectedValues)
{
Node node = _nodeList.FirstOrDefault(i => i.Object != null && i.Object.ToString() != "All" && i.Object.GetPropertyValue(SelectedValuePath) == item);
if (node != null)
node.IsSelected = true;
}
}
}
private void SetSelectedItems()
{
if (SelectedItems != null)
{
SelectedItems.Clear();
foreach (Node node in _nodeList)
{
if (node.IsSelected && node.Object.ToString() != "All")
{
if (this.ItemsSource.Count > 0)
{
if (SelectedItems != null)
{
SelectedItems.Add(node.Object);
}
}
}
}
}
if (SelectedValues != null)
{
SelectedValues.Clear();
foreach (Node node in _nodeList)
{
if (node.IsSelected && node.Object.ToString() != "All")
{
if (this.ItemsSource.Count > 0)
{
if (SelectedValues != null)
{
SelectedValues.Add(node.Object.GetPropertyValue(SelectedValuePath));
}
}
}
}
}
}
private void DisplayInControl()
{
_nodeList.Clear();
if (this.ItemsSource.Count > 0)
_nodeList.Add(new Node("All", DisplayMemberPath));
foreach (var item in this.ItemsSource)
{
Node node = new Node(item, DisplayMemberPath);
_nodeList.Add(node);
}
MultiSelectCombo.ItemsSource = _nodeList;
}
private void SetText()
{
StringBuilder displayText = new StringBuilder();
foreach (Node s in _nodeList)
{
if (s.IsSelected == true && s.Object.ToString() == "All")
{
displayText = new StringBuilder();
displayText.Append("All");
break;
}
else if (s.IsSelected == true && s.Object.ToString() != "All")
{
displayText.Append(s.Object);
displayText.Append(',');
}
}
this.Text = displayText.ToString().TrimEnd(new char[] { ',' });
// set DefaultText if nothing else selected
if (string.IsNullOrEmpty(this.Text))
{
this.Text = this.DefaultText;
}
}
#endregion
}
public class Node : INotifyPropertyChanged
{
#region ctor
public Node(object obj, string displayMemberPath)
{
Object = obj;
if (!string.IsNullOrEmpty(displayMemberPath) && Object.ContainsProperty(displayMemberPath))
Title = Object.GetPropertyValue(displayMemberPath).ToString();
else
Title = obj.ToString();
}
#endregion
#region Properties
private object _object;
public object Object
{
get
{
return _object;
}
set
{
_object = value;
NotifyPropertyChanged("Object");
}
}
private string _title;
public string Title
{
get
{
return _title;
}
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
private bool _isSelected;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

View File

@@ -0,0 +1,309 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
public class OutlineText : FrameworkElement
{
private Pen _pen;
private FormattedText _formattedText;
private Geometry _textGeometry;
private PathGeometry _clipGeometry;
static OutlineText()
{
SnapsToDevicePixelsProperty.OverrideMetadata(typeof(OutlineText), new FrameworkPropertyMetadata(true));
UseLayoutRoundingProperty.OverrideMetadata(typeof(OutlineText), new FrameworkPropertyMetadata(true));
}
public static readonly DependencyProperty StrokePositionProperty = DependencyProperty.Register(
"StrokePosition", typeof(StrokePosition), typeof(OutlineText), new PropertyMetadata(default(StrokePosition)));
public StrokePosition StrokePosition
{
get => (StrokePosition)GetValue(StrokePositionProperty);
set => SetValue(StrokePositionProperty, value);
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(OutlineText), new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender, OnFormattedTextInvalidated));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(
"TextAlignment", typeof(TextAlignment), typeof(OutlineText),
new PropertyMetadata(default(TextAlignment), OnFormattedTextUpdated));
public TextAlignment TextAlignment
{
get => (TextAlignment)GetValue(TextAlignmentProperty);
set => SetValue(TextAlignmentProperty, value);
}
public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(
"TextTrimming", typeof(TextTrimming), typeof(OutlineText),
new PropertyMetadata(default(TextTrimming), OnFormattedTextInvalidated));
public TextTrimming TextTrimming
{
get => (TextTrimming)GetValue(TextTrimmingProperty);
set => SetValue(TextTrimmingProperty, value);
}
public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
"TextWrapping", typeof(TextWrapping), typeof(OutlineText),
new PropertyMetadata(TextWrapping.NoWrap, OnFormattedTextInvalidated));
public TextWrapping TextWrapping
{
get => (TextWrapping)GetValue(TextWrappingProperty);
set => SetValue(TextWrappingProperty, value);
}
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
"Fill", typeof(Brush), typeof(OutlineText), new PropertyMetadata(Brushes.Black, OnFormattedTextUpdated));
public Brush Fill
{
get => (Brush)GetValue(FillProperty);
set => SetValue(FillProperty, value);
}
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
"Stroke", typeof(Brush), typeof(OutlineText), new PropertyMetadata(Brushes.Black, OnFormattedTextUpdated));
public Brush Stroke
{
get => (Brush)GetValue(StrokeProperty);
set => SetValue(StrokeProperty, value);
}
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness", typeof(double), typeof(OutlineText), new PropertyMetadata(0d, OnFormattedTextUpdated));
public double StrokeThickness
{
get => (double)GetValue(StrokeThicknessProperty);
set => SetValue(StrokeThicknessProperty, value);
}
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
typeof(OutlineText),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public FontFamily FontFamily
{
get => (FontFamily)GetValue(FontFamilyProperty);
set => SetValue(FontFamilyProperty, value);
}
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(OutlineText),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get => (double)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
typeof(OutlineText),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public FontStretch FontStretch
{
get => (FontStretch)GetValue(FontStretchProperty);
set => SetValue(FontStretchProperty, value);
}
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
typeof(OutlineText),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public FontStyle FontStyle
{
get => (FontStyle)GetValue(FontStyleProperty);
set => SetValue(FontStyleProperty, value);
}
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
typeof(OutlineText),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public FontWeight FontWeight
{
get => (FontWeight)GetValue(FontWeightProperty);
set => SetValue(FontWeightProperty, value);
}
public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register(
"TextDecorations", typeof(TextDecorationCollection), typeof(OutlineText), new PropertyMetadata(null, OnFormattedTextUpdated));
public TextDecorationCollection TextDecorations
{
get => (TextDecorationCollection)GetValue(TextDecorationsProperty);
set => SetValue(TextDecorationsProperty, value);
}
protected override void OnRender(DrawingContext drawingContext)
{
EnsureGeometry();
drawingContext.DrawGeometry(Fill, null, _textGeometry);
if (StrokePosition == StrokePosition.Outside)
{
drawingContext.PushClip(_clipGeometry);
}
else if (StrokePosition == StrokePosition.Inside)
{
drawingContext.PushClip(_textGeometry);
}
drawingContext.DrawGeometry(null, _pen, _textGeometry);
if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
{
drawingContext.Pop();
}
}
private void UpdatePen()
{
_pen = new Pen(Stroke, StrokeThickness);
//if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
//{
// _pen.Thickness = StrokeThickness * 2;
//}
}
private void EnsureFormattedText()
{
if (_formattedText != null || Text == null)
{
return;
}
#if true
_formattedText = new FormattedText(
Text,
CultureInfo.CurrentUICulture,
FlowDirection,
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
FontSize, Fill);
#else
var source = PresentationSource.FromVisual(this);
var dpi = 1.0;
if (source?.CompositionTarget != null)
{
dpi = 96.0 * source.CompositionTarget.TransformToDevice.M11 / 96.0;
}
_formattedText = new FormattedText(
Text,
CultureInfo.CurrentUICulture,
FlowDirection,
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
FontSize, Fill, dpi);
#endif
UpdateFormattedText();
}
private void EnsureGeometry()
{
if (_textGeometry != null)
{
return;
}
EnsureFormattedText();
_textGeometry = _formattedText.BuildGeometry(new Point(0, 0));
if (StrokePosition == StrokePosition.Outside)
{
var geometry = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
_clipGeometry = Geometry.Combine(geometry, _textGeometry, GeometryCombineMode.Exclude, null);
}
}
private void UpdateFormattedText()
{
if (_formattedText == null)
{
return;
}
_formattedText.MaxLineCount = TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
_formattedText.TextAlignment = TextAlignment;
_formattedText.Trimming = TextTrimming;
_formattedText.SetFontSize(FontSize);
_formattedText.SetFontStyle(FontStyle);
_formattedText.SetFontWeight(FontWeight);
_formattedText.SetFontFamily(FontFamily);
_formattedText.SetFontStretch(FontStretch);
_formattedText.SetTextDecorations(TextDecorations);
}
private static void OnFormattedTextUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlineText)d;
outlinedTextBlock.UpdateFormattedText();
outlinedTextBlock._textGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private static void OnFormattedTextInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlineText)d;
outlinedTextBlock._formattedText = null;
outlinedTextBlock._textGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
protected override Size MeasureOverride(Size availableSize)
{
EnsureFormattedText();
// constrain the formatted text according to the available size
// the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions
// the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw
_formattedText.MaxTextWidth = Math.Min(3579139, availableSize.Width);
_formattedText.MaxTextHeight = Math.Max(0.0001d, availableSize.Height);
UpdatePen();
// return the desired size
return new Size(_formattedText.Width, _formattedText.Height);
}
}
public enum StrokePosition
{
Center,
Outside,
Inside
}
}

View File

@@ -0,0 +1,34 @@
<Window x:Class="AIStudio.Wpf.DiagramHelper.Controls.PopupWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner"
x:Name="theView">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{Binding ElementName=theView, Path=DataContext}" />
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Ok"
IsDefault="True"
Click="Ok_Click"
Margin="5"
Width="100"
Height="30" />
<Button Content="Cancel"
IsCancel="True"
Margin="5"
Width="100"
Height="30" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// PopupWindow.xaml 的交互逻辑
/// </summary>
public partial class PopupWindow : Window
{
public PopupWindow()
{
InitializeComponent();
}
private void Ok_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
this.Close();
}
}
}

View File

@@ -0,0 +1,63 @@
<UserControl x:Class="AIStudio.Wpf.DiagramHelper.Controls.PropertiesView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border x:Name="border" BorderThickness="1" BorderBrush="{DynamicResource GrayBrush8}">
<Border.Resources>
<ControlTemplate x:Key="validationErrorTemplate">
<DockPanel>
<Image Source="/AIStudio.Wpf.DiagramHelper;component/Images/error.png" Height="16" Width="16"
DockPanel.Dock="Right" Margin="-18,0,0,0"
ToolTip="{Binding ElementName=adorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</Image>
<AdornedElementPlaceholder x:Name="adorner"/>
</DockPanel>
</ControlTemplate>
<Style x:Key="gridLineStyle" TargetType="Line">
<Setter Property="Stroke" Value="{DynamicResource GrayBrush8}" />
<Setter Property="Stretch" Value="Fill" />
<Setter Property="Grid.ZIndex" Value="1000" />
</Style>
<Style x:Key="gridHorizontalLineStyle" TargetType="Line" BasedOn="{StaticResource gridLineStyle}">
<Setter Property="X2" Value="1" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="Grid.ColumnSpan"
Value="{Binding
Path=ColumnDefinitions.Count,
RelativeSource={RelativeSource AncestorType=Grid}}"/>
</Style>
<Style x:Key="gridVerticalLineStyle" TargetType="Line" BasedOn="{StaticResource gridLineStyle}">
<Setter Property="Y2" Value="1" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Grid.RowSpan"
Value="{Binding
Path=RowDefinitions.Count,
RelativeSource={RelativeSource AncestorType=Grid}}"/>
</Style>
</Border.Resources>
<DockPanel x:Name="_panel">
<Border x:Name="_label" Width="50" Height="16">
<TextBlock Text="Empty" TextAlignment="Center" Foreground="Gray"/>
</Border>
<ScrollViewer x:Name="_gridContainer" VerticalScrollBarVisibility="Auto">
<Grid x:Name="_grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Line Name="_vLine" Grid.Column="0" Grid.RowSpan="1000" Style="{StaticResource gridVerticalLineStyle}"/>
<GridSplitter Name="_splitter" Grid.RowSpan="1000" Margin="0,0,0,0" Width="1"
Background="{DynamicResource GrayBrush8}" Grid.ZIndex="10000"/>
</Grid>
</ScrollViewer>
</DockPanel>
</Border>
</UserControl>

View File

@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Reflection;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// Interaction logic for PropertiesView.xaml
/// </summary>
public partial class PropertiesView : UserControl
{
#region SelectedObject
public static readonly DependencyProperty SelectedObjectProperty = DependencyProperty.Register("SelectedObject", typeof(object), typeof(PropertiesView), new UIPropertyMetadata(null, OnSelectedObjectChanged));
public object SelectedObject
{
get
{
return (object)GetValue(SelectedObjectProperty);
}
set
{
SetValue(SelectedObjectProperty, value);
}
}
private static void OnSelectedObjectChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
PropertiesView propertyInspector = o as PropertiesView;
if (propertyInspector != null)
propertyInspector.OnSelectedObjectChanged((object)e.OldValue, (object)e.NewValue);
}
protected virtual void OnSelectedObjectChanged(object oldValue, object newValue)
{
// We do not want to process the change now if the grid is initializing (ie. BeginInit/EndInit).
var obj = oldValue as INotifyPropertyChanged;
if (obj != null)
obj.PropertyChanged -= PropertyChanged;
DisplayProperties();
obj = newValue as INotifyPropertyChanged;
if (obj != null)
obj.PropertyChanged += PropertyChanged;
}
#endregion //SelectedObject
public static readonly DependencyProperty NeedBrowsableProperty = DependencyProperty.Register("NeedBrowsable", typeof(bool), typeof(PropertiesView), new UIPropertyMetadata(true));
public bool NeedBrowsable
{
get
{
return (bool)GetValue(NeedBrowsableProperty);
}
set
{
SetValue(NeedBrowsableProperty, value);
}
}
public PropertiesView()
{
InitializeComponent();
this.Loaded += PropertiesView_Loaded;
}
private void PropertiesView_Loaded(object sender, RoutedEventArgs e)
{
DisplayProperties();
}
void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
DisplayProperties();
}
private void DisplayProperties()
{
_panel.Children.Clear();
ClearGrid();
if (SelectedObject != null)
{
int row = 0;
foreach (var prop in SelectedObject.GetType().GetProperties())
{
var attr = prop.GetCustomAttributes(typeof(BrowsableAttribute), true);
if (NeedBrowsable == false && (attr.Length == 0 || (attr[0] as BrowsableAttribute).Browsable))
{
DisplayProperty(prop, row);
row++;
}
else if(NeedBrowsable == true && (attr.Length > 0 && (attr[0] as BrowsableAttribute).Browsable))
{
DisplayProperty(prop, row);
row++;
}
}
_panel.Children.Add(_gridContainer);
}
else
{
_panel.Children.Add(_label);
}
}
private void ClearGrid()
{
_grid.RowDefinitions.Clear();
for (int i = _grid.Children.Count - 1; i >= 0; i--)
{
if (_grid.Children[i] != _vLine && _grid.Children[i] != _splitter)
_grid.Children.RemoveAt(i);
}
}
private void DisplayProperty(PropertyInfo prop, int row)
{
var rowDef = new RowDefinition();
rowDef.Height = new GridLength(Math.Max(20, this.FontSize * 2));
_grid.RowDefinitions.Add(rowDef);
var tb = new TextBlock() { Text = prop.Name };
var displayAttr = prop.GetCustomAttributes(typeof(DisplayNameAttribute), true);
if (displayAttr.Length > 0 )
{
tb.Text = (displayAttr[0] as DisplayNameAttribute).DisplayName;
}
tb.Margin = new Thickness(4);
tb.VerticalAlignment = VerticalAlignment.Center;
Grid.SetColumn(tb, 0);
Grid.SetRow(tb, _grid.RowDefinitions.Count - 1);
_grid.Children.Add(tb);
var line = new Line();
line.Style = (Style)border.Resources["gridHorizontalLineStyle"];
Grid.SetRow(line, row);
_grid.Children.Add(line);
Style style = null;
var styleNameAttr = prop.GetCustomAttributes(typeof(StyleNameAttribute), true);
if (styleNameAttr.Length > 0)
{
style = this.FindResource((styleNameAttr[0] as StyleNameAttribute).Name) as Style;
if (style != null)
{
ContentControl content = new ContentControl();
content.Style = style;
content.DataContext = SelectedObject;
Grid.SetColumn(content, 1);
Grid.SetRow(content, _grid.RowDefinitions.Count - 1);
_grid.Children.Add(content);
}
}
if (style == null)
{
var ed = new TextBox();
ed.PreviewKeyDown += new KeyEventHandler(ed_KeyDown);
ed.Margin = new Thickness(0);
ed.VerticalAlignment = VerticalAlignment.Center;
ed.BorderThickness = new Thickness(0);
Grid.SetColumn(ed, 1);
Grid.SetRow(ed, _grid.RowDefinitions.Count - 1);
var binding = new Binding(prop.Name);
binding.Source = SelectedObject;
binding.ValidatesOnExceptions = true;
binding.Mode = BindingMode.OneWay;
if (prop.CanWrite)
{
var mi = prop.GetSetMethod();
if (mi != null && mi.IsPublic)
binding.Mode = BindingMode.TwoWay;
}
ed.SetBinding(TextBox.TextProperty, binding);
var template = (ControlTemplate)border.Resources["validationErrorTemplate"];
Validation.SetErrorTemplate(ed, template);
_grid.Children.Add(ed);
}
}
void ed_KeyDown(object sender, KeyEventArgs e)
{
var ed = sender as TextBox;
if (ed != null)
{
if (e.Key == Key.Enter)
{
ed.GetBindingExpression(TextBox.TextProperty).UpdateSource();
e.Handled = true;
}
else if (e.Key == Key.Escape)
ed.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
}
}
[AttributeUsage(AttributeTargets.Property)]
public class StyleNameAttribute : Attribute
{
private string _name;
public StyleNameAttribute(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
}
}
}

View File

@@ -0,0 +1,347 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
public class RectangleGridEventArgs
{
public int Row { get; set; }
public int Column { get; set; }
public RectangleGridEventArgs(int row, int column)
{
Row = row;
Column = column;
}
}
public class RectangleGrid : Grid
{
public static readonly DependencyProperty RowNumProperty = DependencyProperty.Register("RowNum", typeof(int),
typeof(RectangleGrid),
new FrameworkPropertyMetadata(4));
public int RowNum
{
get
{
return (int)GetValue(RowNumProperty);
}
set
{
SetValue(RowNumProperty, value);
}
}
public static readonly DependencyProperty ColumnNumProperty = DependencyProperty.Register("ColumnNum", typeof(int),
typeof(RectangleGrid),
new FrameworkPropertyMetadata(4));
public int ColumnNum
{
get
{
return (int)GetValue(ColumnNumProperty);
}
set
{
SetValue(ColumnNumProperty, value);
}
}
public static readonly DependencyProperty SelectRowProperty = DependencyProperty.Register("SelectRow", typeof(int),
typeof(RectangleGrid),
new FrameworkPropertyMetadata(0));
public int SelectRow
{
get
{
return (int)GetValue(SelectRowProperty);
}
set
{
SetValue(SelectRowProperty, value);
}
}
public static readonly DependencyProperty SelectColumnProperty = DependencyProperty.Register("SelectColumn", typeof(int),
typeof(RectangleGrid),
new FrameworkPropertyMetadata(0));
public int SelectColumn
{
get
{
return (int)GetValue(SelectColumnProperty);
}
set
{
SetValue(SelectColumnProperty, value);
}
}
public static readonly DependencyProperty SelectProperty = DependencyProperty.Register("Select", typeof(RectangleGridEventArgs),
typeof(RectangleGrid),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSelectChanged)));
public RectangleGridEventArgs Select
{
get
{
return (RectangleGridEventArgs)GetValue(SelectProperty);
}
set
{
SetValue(SelectProperty, value);
}
}
#region MouseOverBrush
public static readonly DependencyProperty MouseOverBrushProperty = DependencyProperty.Register(
"MouseOverBrush", typeof(Brush), typeof(RectangleGrid), new PropertyMetadata(null));
public Brush MouseOverBrush
{
get { return (Brush)GetValue(MouseOverBrushProperty); }
set { SetValue(MouseOverBrushProperty, value); }
}
#endregion
private Rectangle[,] rectangles;
public RectangleGrid()
{
this.Loaded += RectangleGrid_Loaded;
}
void RectangleGrid_Loaded(object sender, RoutedEventArgs e)
{
this.Loaded -= RectangleGrid_Loaded;
for (int i = 0; i < RowNum; i++)
{
RowDefinition row = new RowDefinition() { Height = new GridLength(2, GridUnitType.Auto) };
this.RowDefinitions.Add(row);
}
for (int j = 0; j < ColumnNum; j++)
{
ColumnDefinition col = new ColumnDefinition() { Width = new GridLength(2, GridUnitType.Auto) };
this.ColumnDefinitions.Add(col);
}
rectangles = new Rectangle[RowNum, ColumnNum];
for (int i = 0; i < rectangles.GetLength(0); i++)
{
for (int j = 0; j < rectangles.GetLength(1); j++)
{
Rectangle rectangle = new Rectangle();
rectangle.IsHitTestVisible = true;
rectangle.SnapsToDevicePixels = true;
rectangle.Stroke = new SolidColorBrush(Colors.Black);
rectangle.Width = 24;
rectangle.Height = 24;
rectangle.Margin = new Thickness(2);
rectangle.MouseEnter += rectangle_MouseEnter;
rectangle.MouseLeave += rectangle_MouseLeave;
rectangle.MouseLeftButtonDown += rectangle_MouseLeftButtonDown;
rectangle.Fill = new SolidColorBrush(Color.FromArgb(0xff, 0xff, 0xff, 0xff));
Grid.SetRow(rectangle, i);
Grid.SetColumn(rectangle, j);
this.Children.Add(rectangle);
rectangles[i, j] = rectangle;
}
}
}
void rectangle_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Brush myBrush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xff, 0x30, 0x50, 0x68));
Brush myBrush2 = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xff, 0xff, 0xff, 0xff));
if (MouseOverBrush != null)
{
myBrush = MouseOverBrush;
}
#region
Rectangle rect = sender as Rectangle;
var rects = rectangles.Cast<Rectangle>().ToArray();
int index = Array.IndexOf(rects, rect);
int x = index / rectangles.GetLength(1);
int y = index % rectangles.GetLength(1);
FillRectangle(x, y);
#endregion
}
private void FillRectangle(int row, int column)
{
Brush myBrush = new SolidColorBrush(Color.FromArgb(0xff, 0x30, 0x50, 0x68));
Brush myBrush2 = new SolidColorBrush(Color.FromArgb(0xff, 0xff, 0xff, 0xff));
if (MouseOverBrush != null)
{
myBrush = MouseOverBrush;
}
for (int i = 0; i < rectangles.GetLength(0); i++)
{
for (int j = 0; j < rectangles.GetLength(1); j++)
{
if (i <= row && j <= column)
{
rectangles[i, j].Fill = myBrush;
}
else
{
rectangles[i, j].Fill = myBrush2;
}
}
}
}
void rectangle_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
}
void rectangle_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
Rectangle rect = sender as Rectangle;
var rects = rectangles.Cast<Rectangle>().ToArray();
int index = Array.IndexOf(rects, rect);
int x = index / rectangles.GetLength(1);
int y = index % rectangles.GetLength(1);
this.SelectRow = x + 1;
this.SelectColumn = y + 1;
this.Select = new RectangleGridEventArgs(this.SelectRow, this.SelectColumn);
}
private static void OnSelectRowChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RectangleGrid grid = (RectangleGrid)sender;
int oldRow = (int)e.OldValue;
int newRow = (int)e.NewValue;
if (oldRow == 0 || newRow == 0)
{
grid.FillRectangle((int)e.NewValue, grid.SelectColumn);
}
//避免两次触发
var oldArgs = new RectangleGridEventArgs(oldRow, grid.SelectColumn);
var newArgs = new RectangleGridEventArgs(newRow, grid.SelectColumn);
grid.OnSelectColumnChanged(oldArgs, newArgs);
}
private static void OnSelectColumnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RectangleGrid grid = (RectangleGrid)sender;
int oldColumn = (int)e.OldValue;
int newColumn = (int)e.NewValue;
grid.FillRectangle(grid.SelectRow, (int)e.NewValue);
var oldArgs = new RectangleGridEventArgs(grid.SelectRow, oldColumn);
var newArgs = new RectangleGridEventArgs(grid.SelectRow, newColumn);
grid.OnSelectColumnChanged(oldArgs, newArgs);
}
private static void OnSelectChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RectangleGrid grid = (RectangleGrid)sender;
RectangleGridEventArgs oldArgs = e.OldValue as RectangleGridEventArgs;
RectangleGridEventArgs newArgs = e.NewValue as RectangleGridEventArgs;
//grid.FillRectangle(newArgs.Row, newArgs.Column);
grid.OnSelectColumnChanged(oldArgs, newArgs);
}
public static readonly RoutedEvent SelectRowColumnChangedEvent =
EventManager.RegisterRoutedEvent("SelectRowColumnChanged", RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<RectangleGridEventArgs>), typeof(RectangleGrid));
public event RoutedPropertyChangedEventHandler<RectangleGridEventArgs> SelectRowColumnChanged
{
add { AddHandler(SelectRowColumnChangedEvent, value); }
remove { RemoveHandler(SelectRowColumnChangedEvent, value); }
}
private void OnSelectColumnChanged(RectangleGridEventArgs oldargs, RectangleGridEventArgs newargs)
{
RoutedPropertyChangedEventArgs<RectangleGridEventArgs> args = new RoutedPropertyChangedEventArgs<RectangleGridEventArgs>(oldargs, newargs);
args.RoutedEvent = RectangleGrid.SelectRowColumnChangedEvent;
RaiseEvent(args);
switch (Command)
{
case null:
return;
case RoutedCommand command:
command.Execute(CommandParameter, CommandTarget);
break;
default:
Command.Execute(CommandParameter);
break;
}
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(RectangleGrid), new PropertyMetadata(default(ICommand), OnCommandChanged));
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (RectangleGrid)d;
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= ctl.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += ctl.CanExecuteChanged;
}
}
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter", typeof(object), typeof(RectangleGrid), new PropertyMetadata(default(object)));
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
"CommandTarget", typeof(IInputElement), typeof(RectangleGrid), new PropertyMetadata(default(IInputElement)));
public IInputElement CommandTarget
{
get => (IInputElement)GetValue(CommandTargetProperty);
set => SetValue(CommandTargetProperty, value);
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (Command == null) return;
IsEnabled = Command is RoutedCommand command
? command.CanExecute(CommandParameter, CommandTarget)
: Command.CanExecute(CommandParameter);
}
}
}

View File

@@ -0,0 +1,38 @@
<UserControl x:Class="AIStudio.Wpf.DiagramHelper.Controls.SliderRotation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:AIStudio.Wpf.DiagramHelper.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Name="sliderRotation"
Foreground="{DynamicResource Fluent.Ribbon.Brushes.AccentBaseColorBrush}" BorderBrush="{DynamicResource BlackBrush}" >
<Viewbox>
<Grid>
<Canvas Name="canvas" Width="80" Height="80" HorizontalAlignment="Center" VerticalAlignment="Center">
<Ellipse Height="80" Width="80" Name="ellipseBack"
Stroke="{x:Null}"
Fill="{Binding ElementName=sliderRotation, Path=BorderBrush}"
MouseLeave="ellipseBack_MouseLeave"
MouseMove="ellipseBack_MouseMove"
MouseLeftButtonUp="ellipseBack_MouseUp"
MouseLeftButtonDown="ellipseBack_MouseDown"
StrokeThickness="0.5">
<!--<Ellipse.Effect>-->
<!--Effect外部光圈-->
<!--
<DropShadowEffect Direction="270" Color="Black" Opacity="0.5" BlurRadius="3" ShadowDepth="1"/>
</Ellipse.Effect>-->
</Ellipse>
<Ellipse Height="20" Name="handle" Stroke="#20000000" Width="20" Fill="{Binding ElementName=sliderRotation, Path=Foreground}" RenderTransformOrigin="0.5,2"
IsHitTestVisible="False" Margin="30,0,0,0">
<Ellipse.RenderTransform>
<RotateTransform x:Name="rotate" Angle="{Binding ElementName=sliderRotation, Path=Value}"/>
<!--RotateTransform能够让某对象产生旋转变化根据中心点进行顺时针旋转或逆时针旋转。-->
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
</Grid>
</Viewbox>
</UserControl>

View File

@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// SliderRotation.xaml 的交互逻辑
/// </summary>
public partial class SliderRotation : UserControl
{
#region
private Point cen; //中心点
private Point first; //初始状态位置
private Point second; //
private bool flag = false;
#endregion
#region
/// <summary>
/// 获取或设置Value数值
/// </summary>
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached(
"Value",
typeof(double),
typeof(SliderRotation),
new FrameworkPropertyMetadata((double)0, new PropertyChangedCallback(ValuePropertyChangedCallback)));
#endregion
public SliderRotation()
{
InitializeComponent();
}
private static void ValuePropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
{
if (sender != null && sender is SliderRotation)
{
SliderRotation sliderRotation = sender as SliderRotation;
RoutedPropertyChangedEventArgs<object> valueArg =
new RoutedPropertyChangedEventArgs<object>(arg.OldValue, arg.NewValue, ValueChangedEvent);
sliderRotation.RaiseEvent(valueArg);
}
}
#region
public event RoutedPropertyChangedEventHandler<object> ValueChanged
{
add
{
this.AddHandler(ValueChangedEvent, value);
}
remove
{
this.RemoveHandler(ValueChangedEvent, value);
}
}
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
"ValueChanged",
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<object>),
typeof(SliderRotation));
#endregion
private double GetAngle(Point point) //获取点到中心的角度 构造平面直角坐标系 计算点在该坐标系与y轴正方向的夹角
{
const double M_PI = 3.1415926535897;
if (point.X >= 0)
{
double hypotenuse = Math.Sqrt(point.X * point.X + point.Y * point.Y);
return Math.Acos(point.Y / hypotenuse) * 180 / M_PI;
}
else
{
double hypotenuse = Math.Sqrt(point.X * point.X + point.Y * point.Y);
return 360 - Math.Acos(point.Y / hypotenuse) * 180 / M_PI;
}
}
private void ellipseBack_MouseDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
flag = true;
cen = new Point(ellipseBack.Width / 2, ellipseBack.Height / 2);
first = new Point(e.GetPosition(canvas).X - cen.X, cen.Y - e.GetPosition(canvas).Y);
}
private void ellipseBack_MouseUp(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
flag = false;
}
private void ellipseBack_MouseLeave(object sender, MouseEventArgs e)
{
e.Handled = true;
flag = false;
}
private void ellipseBack_MouseMove(object sender, MouseEventArgs e)
{
e.Handled = true; //停止路由事件向上传递
if (flag)
{
second = new Point(e.GetPosition(canvas).X - cen.X, cen.Y - e.GetPosition(canvas).Y); //确定鼠标移动的点坐标(相对中心点的位置)
if (second == new Point(0, 0))
{
return;
}
double anglePointToPoint = GetAngle(second) - GetAngle(first); //得到鼠标移动之前与鼠标移动之后之间的夹角
first = second;
if (Math.Abs(anglePointToPoint) > 90) //夹角如果大于90度忽略(大于90度的夹角有可能是计算错误得出来的)
{
anglePointToPoint = 0;
}
var angle = Value + anglePointToPoint;
if (angle < 0)
{
angle += 360;
}
angle = angle % 360;
Value = Math.Round(angle, 0); //计算出旋转角度
}
}
}
}

View File

@@ -0,0 +1,618 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
namespace AIStudio.Wpf.DiagramHelper.Controls
{
/// <summary>
/// A ruler control which supports both centimeters and inches. In order to use it vertically, change the <see cref="Marks">Marks</see> property to <c>Up</c> and rotate it ninety degrees.
/// </summary>
/// <remarks>
/// Contributions from <see
/// cref="http://www.orbifold.net/default/?p=2295&amp;cpage=1#comment-61500">Raf
/// Lenfers</see>
/// </remarks>
public class Ruler : FrameworkElement
{
#region Fields
private double SegmentHeight;
private readonly Pen p = new Pen(Brushes.Black, 1.0);
private readonly Pen ThinPen = new Pen(Brushes.Black, 0.5);
private readonly Pen BorderPen = new Pen(Brushes.Gray, 1.0);
private readonly Pen RedPen = new Pen(Brushes.Red, 2.0);
#endregion
#region Properties
#region Background
public Brush Background
{
get
{
return (Brush)GetValue(BackgroundProperty);
}
set
{
SetValue(BackgroundProperty, value);
}
}
/// <summary>
/// Identifies the Length dependency property.
/// </summary>
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register(
"Background",
typeof(Brush),
typeof(Ruler),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region Length
/// <summary>
/// Gets or sets the length of the ruler. If the <see cref="AutoSize"/> property is set to false (default) this
/// is a fixed length. Otherwise the length is calculated based on the actual width of the ruler.
/// </summary>
public double Length
{
get
{
if (this.AutoSize)
{
return (double)(Unit == Unit.Cm ? DipHelper.DipToCm(this.ActualWidth) : DipHelper.DipToInch(this.ActualWidth)) / this.Zoom;
}
else
{
return (double)GetValue(LengthProperty);
}
}
set
{
SetValue(LengthProperty, value);
}
}
/// <summary>
/// Identifies the Length dependency property.
/// </summary>
public static readonly DependencyProperty LengthProperty =
DependencyProperty.Register(
"Length",
typeof(double),
typeof(Ruler),
new FrameworkPropertyMetadata(20D, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region AutoSize
/// <summary>
/// Gets or sets the AutoSize behavior of the ruler.
/// false (default): the lenght of the ruler results from the <see cref="Length"/> property. If the window size is changed, e.g. wider
/// than the rulers length, free space is shown at the end of the ruler. No rescaling is done.
/// true : the length of the ruler is always adjusted to its actual width. This ensures that the ruler is shown
/// for the actual width of the window.
/// </summary>
public bool AutoSize
{
get
{
return (bool)GetValue(AutoSizeProperty);
}
set
{
SetValue(AutoSizeProperty, value);
this.InvalidateVisual();
}
}
/// <summary>
/// Identifies the AutoSize dependency property.
/// </summary>
public static readonly DependencyProperty AutoSizeProperty =
DependencyProperty.Register(
"AutoSize",
typeof(bool),
typeof(Ruler),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region Zoom
/// <summary>
/// Gets or sets the zoom factor for the ruler. The default value is 1.0.
/// </summary>
public double Zoom
{
get
{
return (double)GetValue(ZoomProperty);
}
set
{
SetValue(ZoomProperty, value);
this.InvalidateVisual();
}
}
/// <summary>
/// Identifies the Zoom dependency property.
/// </summary>
public static readonly DependencyProperty ZoomProperty =
DependencyProperty.Register("Zoom", typeof(double), typeof(Ruler),
new FrameworkPropertyMetadata((double)1.0,
FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region Chip
/// <summary>
/// Chip Dependency Property
/// </summary>
public static readonly DependencyProperty ChipProperty =
DependencyProperty.Register("Chip", typeof(double), typeof(Ruler),
new FrameworkPropertyMetadata((double)-1000,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Sets the location of the chip in the units of the ruler.
/// So, to set the chip to 10 in cm units the chip needs to be set to 10.
/// Use the <see cref="DipHelper"/> class for conversions.
/// </summary>
public double Chip
{
get { return (double)GetValue(ChipProperty); }
set { SetValue(ChipProperty, value); }
}
#endregion
#region CountShift
/// <summary>
/// CountShift Dependency Property
/// </summary>
public static readonly DependencyProperty CountShiftProperty =
DependencyProperty.Register("CountShift", typeof(double), typeof(Ruler),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// By default the counting of inches or cm starts at zero, this property allows you to shift
/// the counting.
/// </summary>
public double CountShift
{
get { return (double)GetValue(CountShiftProperty); }
set { SetValue(CountShiftProperty, value); }
}
#endregion
#region Marks
/// <summary>
/// Marks Dependency Property
/// </summary>
public static readonly DependencyProperty MarksProperty =
DependencyProperty.Register("Marks", typeof(MarksLocation), typeof(Ruler),
new FrameworkPropertyMetadata(MarksLocation.Up,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Gets or sets where the marks are shown in the ruler.
/// </summary>
public MarksLocation Marks
{
get { return (MarksLocation)GetValue(MarksProperty); }
set { SetValue(MarksProperty, value); }
}
#endregion
#region Unit
/// <summary>
/// Gets or sets the unit of the ruler.
/// Default value is Unit.Cm.
/// </summary>
public Unit Unit
{
get { return (Unit)GetValue(UnitProperty); }
set { SetValue(UnitProperty, value); }
}
/// <summary>
/// Identifies the Unit dependency property.
/// </summary>
public static readonly DependencyProperty UnitProperty =
DependencyProperty.Register(
"Unit",
typeof(Unit),
typeof(Ruler),
new FrameworkPropertyMetadata(Unit.Cm, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#endregion
#region Constructor
static Ruler()
{
HeightProperty.OverrideMetadata(typeof(Ruler), new FrameworkPropertyMetadata(20.0));
}
public Ruler()
{
SegmentHeight = this.Height - 10;
}
#endregion
#region Methods
/// <summary>
/// Participates in rendering operations.
/// </summary>
/// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param>
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height);
drawingContext.DrawRectangle(Background, null, rect);
double xDest = (Unit == Unit.Cm ? DipHelper.CmToDip(Length) : DipHelper.InchToDip(Length)) * this.Zoom;
drawingContext.DrawRectangle(null, BorderPen, new Rect(new Point(0.0, 0.0), new Point(xDest, Height)));
double chip = Unit == Unit.Cm ? DipHelper.CmToDip(Chip) : DipHelper.InchToDip(Chip);
drawingContext.DrawLine(RedPen, new Point(chip, 0), new Point(chip, Height));
//画偏移位置之前的
if (CountShift < 0)
{
for (double dUnit = -CountShift; dUnit > -1; dUnit--)
{
double d;
if (Unit == Unit.Cm)
{
d = DipHelper.CmToDip(dUnit) * this.Zoom;
if (dUnit < Length)
{
for (int i = 1; i <= 9; i++)
{
if (i != 5)
{
double dMm = DipHelper.CmToDip(dUnit + 0.1 * i) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(ThinPen, new Point(dMm, 0), new Point(dMm, SegmentHeight / 3.0));
else
drawingContext.DrawLine(ThinPen, new Point(dMm, Height), new Point(dMm, Height - SegmentHeight / 3.0));
}
}
double dMiddle = DipHelper.CmToDip(dUnit + 0.5) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(dMiddle, 0), new Point(dMiddle, SegmentHeight * 2.0 / 3.0));
else
drawingContext.DrawLine(p, new Point(dMiddle, Height), new Point(dMiddle, Height - SegmentHeight * 2.0 / 3.0));
}
}
else
{
d = DipHelper.InchToDip(dUnit) * this.Zoom;
if (dUnit < Length)
{
if (Marks == MarksLocation.Up)
{
double dQuarter = DipHelper.InchToDip(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, 0),
new Point(dQuarter, SegmentHeight / 3.0));
double dMiddle = DipHelper.InchToDip(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, 0),
new Point(dMiddle, SegmentHeight * 2D / 3D));
double d3Quarter = DipHelper.InchToDip(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, 0),
new Point(d3Quarter, SegmentHeight / 3.0));
}
else
{
double dQuarter = DipHelper.InchToDip(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, Height),
new Point(dQuarter, Height - SegmentHeight / 3.0));
double dMiddle = DipHelper.InchToDip(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, Height),
new Point(dMiddle, Height - SegmentHeight * 2D / 3D));
double d3Quarter = DipHelper.InchToDip(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, Height),
new Point(d3Quarter, Height - SegmentHeight / 3.0));
}
}
}
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(d, 0), new Point(d, SegmentHeight));
else
drawingContext.DrawLine(p, new Point(d, Height), new Point(d, Height - SegmentHeight));
if ((dUnit != 0.0) && (dUnit < Length))
{
FormattedText ft = new FormattedText(
(Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
DipHelper.PtToDip(6),
Brushes.DimGray);
ft.SetFontWeight(FontWeights.Regular);
ft.TextAlignment = TextAlignment.Center;
if (Marks == MarksLocation.Up)
drawingContext.DrawText(ft, new Point(d, Height - ft.Height));
else
drawingContext.DrawText(ft, new Point(d, Height - SegmentHeight - ft.Height));
}
}
}
//画偏移位置之后的
for (double dUnit = -CountShift; dUnit <= Length; dUnit++)
{
double d;
if (Unit == Unit.Cm)
{
d = DipHelper.CmToDip(dUnit) * this.Zoom;
if (dUnit < Length)
{
for (int i = 1; i <= 9; i++)
{
if (i != 5)
{
double dMm = DipHelper.CmToDip(dUnit + 0.1 * i) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(ThinPen, new Point(dMm, 0), new Point(dMm, SegmentHeight / 3.0));
else
drawingContext.DrawLine(ThinPen, new Point(dMm, Height), new Point(dMm, Height - SegmentHeight / 3.0));
}
}
double dMiddle = DipHelper.CmToDip(dUnit + 0.5) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(dMiddle, 0), new Point(dMiddle, SegmentHeight * 2.0 / 3.0));
else
drawingContext.DrawLine(p, new Point(dMiddle, Height), new Point(dMiddle, Height - SegmentHeight * 2.0 / 3.0));
}
}
else
{
d = DipHelper.InchToDip(dUnit) * this.Zoom;
if (dUnit < Length)
{
if (Marks == MarksLocation.Up)
{
double dQuarter = DipHelper.InchToDip(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, 0),
new Point(dQuarter, SegmentHeight / 3.0));
double dMiddle = DipHelper.InchToDip(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, 0),
new Point(dMiddle, SegmentHeight * 2D / 3D));
double d3Quarter = DipHelper.InchToDip(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, 0),
new Point(d3Quarter, SegmentHeight / 3.0));
}
else
{
double dQuarter = DipHelper.InchToDip(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, Height),
new Point(dQuarter, Height - SegmentHeight / 3.0));
double dMiddle = DipHelper.InchToDip(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, Height),
new Point(dMiddle, Height - SegmentHeight * 2D / 3D));
double d3Quarter = DipHelper.InchToDip(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, Height),
new Point(d3Quarter, Height - SegmentHeight / 3.0));
}
}
}
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(d, 0), new Point(d, SegmentHeight));
else
drawingContext.DrawLine(p, new Point(d, Height), new Point(d, Height - SegmentHeight));
if ((dUnit != 0.0) && (dUnit < Length))
{
FormattedText ft = new FormattedText(
(Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
DipHelper.PtToDip(6),
Brushes.DimGray);
ft.SetFontWeight(FontWeights.Regular);
ft.TextAlignment = TextAlignment.Center;
if (Marks == MarksLocation.Up)
drawingContext.DrawText(ft, new Point(d, Height - ft.Height));
else
drawingContext.DrawText(ft, new Point(d, Height - SegmentHeight - ft.Height));
}
}
}
/// <summary>
/// Measures an instance during the first layout pass prior to arranging it.
/// </summary>
/// <param name="availableSize">A maximum Size to not exceed.</param>
/// <returns>The maximum Size for the instance.</returns>
protected override Size MeasureOverride(Size availableSize)
{
return base.MeasureOverride(availableSize);
//Size desiredSize;
//if (Unit == Unit.Cm)
//{
// desiredSize = new Size(DipHelper.CmToDip(Length), Height);
//}
//else
//{
// desiredSize = new Size(DipHelper.InchToDip(Length), Height);
//}
//return desiredSize;
}
#endregion
}
/// <summary>
/// The unit type of the ruler.
/// </summary>
public enum Unit
{
/// <summary>
/// the unit is Centimeter.
/// </summary>
Cm,
/// <summary>
/// The unit is Inch.
/// </summary>
Inch
};
public enum MarksLocation
{
Up, Down
}
/// <summary>
/// A helper class for DIP (Device Independent Pixels) conversion and scaling operations.
/// </summary>
public static class DipHelper
{
/// <summary>
/// Converts millimeters to DIP (Device Independant Pixels).
/// </summary>
/// <param name="mm">A millimeter value.</param>
/// <returns>A DIP value.</returns>
public static double MmToDip(double mm)
{
return CmToDip(mm / 10.0);
}
/// <summary>
/// Converts centimeters to DIP (Device Independant Pixels).
/// </summary>
/// <param name="cm">A centimeter value.</param>
/// <returns>A DIP value.</returns>
public static double CmToDip(double cm)
{
return (cm * 96.0 / 2.54);
}
/// <summary>
/// Converts inches to DIP (Device Independant Pixels).
/// </summary>
/// <param name="inch">An inch value.</param>
/// <returns>A DIP value.</returns>
public static double InchToDip(double inch)
{
return (inch * 96.0);
}
public static double DipToInch(double dip)
{
return dip / 96D;
}
/// <summary>
/// Converts font points to DIP (Device Independant Pixels).
/// </summary>
/// <param name="pt">A font point value.</param>
/// <returns>A DIP value.</returns>
public static double PtToDip(double pt)
{
return (pt * 96.0 / 72.0);
}
/// <summary>
/// Converts DIP (Device Independant Pixels) to centimeters.
/// </summary>
/// <param name="dip">A DIP value.</param>
/// <returns>A centimeter value.</returns>
public static double DipToCm(double dip)
{
return (dip * 2.54 / 96.0);
}
/// <summary>
/// Converts DIP (Device Independant Pixels) to millimeters.
/// </summary>
/// <param name="dip">A DIP value.</param>
/// <returns>A millimeter value.</returns>
public static double DipToMm(double dip)
{
return DipToCm(dip) * 10.0;
}
/// <summary>
/// Gets the system DPI scale factor (compared to 96 dpi).
/// From http://blogs.msdn.com/jaimer/archive/2007/03/07/getting-system-dpi-in-wpf-app.aspx
/// Should not be called before the Loaded event (else XamlException mat throw)
/// </summary>
/// <returns>A Point object containing the X- and Y- scale factor.</returns>
private static Point GetSystemDpiFactor()
{
PresentationSource source = PresentationSource.FromVisual(Application.Current.MainWindow);
Matrix m = source.CompositionTarget.TransformToDevice;
return new Point(m.M11, m.M22);
}
private const double DpiBase = 96.0;
/// <summary>
/// Gets the system configured DPI.
/// </summary>
/// <returns>A Point object containing the X- and Y- DPI.</returns>
public static Point GetSystemDpi()
{
Point sysDpiFactor = GetSystemDpiFactor();
return new Point(
sysDpiFactor.X * DpiBase,
sysDpiFactor.Y * DpiBase);
}
/// <summary>
/// Gets the physical pixel density (DPI) of the screen.
/// </summary>
/// <param name="diagonalScreenSize">Size - in inch - of the diagonal of the screen.</param>
/// <returns>A Point object containing the X- and Y- DPI.</returns>
public static Point GetPhysicalDpi(double diagonalScreenSize)
{
Point sysDpiFactor = GetSystemDpiFactor();
double pixelScreenWidth = SystemParameters.PrimaryScreenWidth * sysDpiFactor.X;
double pixelScreenHeight = SystemParameters.PrimaryScreenHeight * sysDpiFactor.Y;
double formatRate = pixelScreenWidth / pixelScreenHeight;
double inchHeight = diagonalScreenSize / Math.Sqrt(formatRate * formatRate + 1.0);
double inchWidth = formatRate * inchHeight;
double xDpi = Math.Round(pixelScreenWidth / inchWidth);
double yDpi = Math.Round(pixelScreenHeight / inchHeight);
return new Point(xDpi, yDpi);
}
/// <summary>
/// Converts a DPI into a scale factor (compared to system DPI).
/// </summary>
/// <param name="dpi">A Point object containing the X- and Y- DPI to convert.</param>
/// <returns>A Point object containing the X- and Y- scale factor.</returns>
public static Point DpiToScaleFactor(Point dpi)
{
Point sysDpi = GetSystemDpi();
return new Point(
dpi.X / sysDpi.X,
dpi.Y / sysDpi.Y);
}
/// <summary>
/// Gets the scale factor to apply to a WPF application
/// so that 96 DIP always equals 1 inch on the screen (whatever the system DPI).
/// </summary>
/// <param name="diagonalScreenSize">Size - in inch - of the diagonal of the screen</param>
/// <returns>A Point object containing the X- and Y- scale factor.</returns>
public static Point GetScreenIndependentScaleFactor(double diagonalScreenSize)
{
return DpiToScaleFactor(GetPhysicalDpi(diagonalScreenSize));
}
}
}