commit f25a9587971e09aa78973899a3e821eb5715b853 Author: akwkevin Date: Fri Jul 23 09:42:22 2021 +0800 添加项目文件。 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6d50fe8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,66 @@ +*.js linguist-language=csharp +*.css linguist-language=csharp +*.html linguist-language=csharp +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d3a70a --- /dev/null +++ b/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +/src/Coldairarrow.Web/wwwroot/Upload/File +/src/Coldairarrow.Api/wwwroot/Upload +/.vs/AIStudio.Wpf.Diagram/v16/.suo diff --git a/AIStudio.Wpf.ADiagram/AIStudio.Wpf.ADiagram.csproj b/AIStudio.Wpf.ADiagram/AIStudio.Wpf.ADiagram.csproj new file mode 100644 index 0000000..082b033 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/AIStudio.Wpf.ADiagram.csproj @@ -0,0 +1,923 @@ + + + + WinExe + net472 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + + + + + + + $(DefaultXamlRuntime) + + + $(DefaultXamlRuntime) + Designer + + + $(DefaultXamlRuntime) + Designer + + + $(DefaultXamlRuntime) + Designer + + + $(DefaultXamlRuntime) + Designer + + + $(DefaultXamlRuntime) + Designer + + + $(DefaultXamlRuntime) + + + $(DefaultXamlRuntime) + + + $(DefaultXamlRuntime) + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + 8.0 + + + + + \ No newline at end of file diff --git a/AIStudio.Wpf.ADiagram/App.config b/AIStudio.Wpf.ADiagram/App.config new file mode 100644 index 0000000..9b0ead3 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/App.config @@ -0,0 +1,28 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AIStudio.Wpf.ADiagram/App.xaml b/AIStudio.Wpf.ADiagram/App.xaml new file mode 100644 index 0000000..f237e9f --- /dev/null +++ b/AIStudio.Wpf.ADiagram/App.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/AIStudio.Wpf.ADiagram/App.xaml.cs b/AIStudio.Wpf.ADiagram/App.xaml.cs new file mode 100644 index 0000000..dacd2f7 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/App.xaml.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +//注意下面的语句一定要加上,指定log4net使用.config文件来读取配置信息 +//如果是WinForm(假定程序为MyDemo.exe,则需要一个MyDemo.exe.config文件) +//如果是WebForm,则从web.config中读取相关信息 +[assembly: log4net.Config.XmlConfigurator(Watch = true)] +namespace AIStudio.Wpf.ADiagram +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + + public App() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + Application.Current.DispatcherUnhandledException += Application_DispatcherUnhandledException; + } + + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + { + //记录严重错误 + log.Fatal(e.Exception); + e.Handled = true;//使用这一行代码告诉运行时,该异常被处理了,不再作为UnhandledException抛出了。 + } + + void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + //记录严重错误 + log.Fatal(e.ExceptionObject); + } + } +} diff --git a/AIStudio.Wpf.ADiagram/ApplicationServicesProvider.cs b/AIStudio.Wpf.ADiagram/ApplicationServicesProvider.cs new file mode 100644 index 0000000..cfd09bd --- /dev/null +++ b/AIStudio.Wpf.ADiagram/ApplicationServicesProvider.cs @@ -0,0 +1,82 @@ +using AIStudio.Wpf.ADiagram.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AIStudio.Wpf.ADiagram +{ + /// + /// Simple service interface + /// + public interface IServiceProvider + { + IUIVisualizerService VisualizerService { get; } + IMessageBoxService MessageBoxService { get; } + //IDatabaseAccessService DatabaseAccessService { get; } + } + + + /// + /// Simple service locator + /// + public class ServiceProvider : IServiceProvider + { + private IUIVisualizerService visualizerService = new WPFUIVisualizerService(); + private IMessageBoxService messageBoxService = new WPFMessageBoxService(); + //private IDatabaseAccessService databaseAccessService = new DatabaseAccessService(); + + public IUIVisualizerService VisualizerService + { + get { return visualizerService; } + } + + public IMessageBoxService MessageBoxService + { + get { return messageBoxService; } + } + + //public IDatabaseAccessService DatabaseAccessService + //{ + // get { return databaseAccessService; } + //} + + } + + + + /// + /// Simple service locator helper + /// + public class ApplicationServicesProvider + { + private static Lazy instance = new Lazy(() => new ApplicationServicesProvider()); + private IServiceProvider serviceProvider = new ServiceProvider(); + + private ApplicationServicesProvider() + { + + } + + static ApplicationServicesProvider() + { + + } + + public void SetNewServiceProvider(IServiceProvider provider) + { + serviceProvider = provider; + } + + public IServiceProvider Provider + { + get { return serviceProvider; } + } + + public static ApplicationServicesProvider Instance + { + get { return instance.Value; } + } + } +} diff --git a/AIStudio.Wpf.ADiagram/AssemblyInfo.cs b/AIStudio.Wpf.ADiagram/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/AIStudio.Wpf.ADiagram/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/AIStudio.Wpf.ADiagram/Commands/CanExecuteDelegateCommand.cs b/AIStudio.Wpf.ADiagram/Commands/CanExecuteDelegateCommand.cs new file mode 100644 index 0000000..ede732c --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/CanExecuteDelegateCommand.cs @@ -0,0 +1,908 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// This class allows delegating the commanding logic to methods passed as parameters, + /// and enables a View to bind commands to objects that are not part of the element tree. + /// + public class CanExecuteDelegateCommand : IDelegateCommand + { + #region Constructors + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod) + : this(executeMethod, null, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod) + : this(executeMethod, canExecuteMethod, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled) + { + if (executeMethod == null) + { + throw new ArgumentNullException("executeMethod"); + } + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; + } + + #endregion + + #region Public Methods + + /// + /// Method to determine if the command can be executed + /// + public bool CanExecute() + { + if (_canExecuteMethod != null) + { + return _canExecuteMethod(); + } + return true; + } + + /// + /// Execution of the command + /// + public void Execute() + { + if (_executeMethod != null) + { + _executeMethod(); + } + } + + /// + /// + /// + public object Target + { + get + { + if (_executeMethod == null) + return null; + + return _executeMethod.Target; + } + } + + /// + /// Property to enable or disable CommandManager's automatic requery on this command + /// + public bool IsAutomaticRequeryDisabled + { + get + { + return _isAutomaticRequeryDisabled; + } + set + { + if (_isAutomaticRequeryDisabled != value) + { + if (value) + { + CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); + } + else + { + CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); + } + _isAutomaticRequeryDisabled = value; + } + } + } + + /// + /// Raises the CanExecuteChaged event + /// + public void RaiseCanExecuteChanged() + { + OnCanExecuteChanged(); + } + + /// + /// Protected virtual method to raise CanExecuteChanged event + /// + protected virtual void OnCanExecuteChanged() + { + CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); + } + + #endregion + + #region ICommand Members + + /// + /// ICommand.CanExecuteChanged implementation + /// + public event EventHandler CanExecuteChanged + { + add + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested += value; + } + CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); + } + remove + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested -= value; + } + CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); + } + } + + bool ICommand.CanExecute(object parameter) + { + return CanExecute(); + } + + void ICommand.Execute(object parameter) + { + Execute(); + } + + #endregion + + #region Data + + private readonly Action _executeMethod = null; + private readonly Func _canExecuteMethod = null; + private bool _isAutomaticRequeryDisabled = false; + private List _canExecuteChangedHandlers; + + #endregion + } + + public class CanExecuteDelegateCommand : IDelegateCommand + { + #region Constructors + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod) + : this(executeMethod, null, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod) + : this(executeMethod, canExecuteMethod, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled) + { + if (executeMethod == null) + { + throw new ArgumentNullException("executeMethod"); + } + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; + } + + #endregion + + #region Public Methods + + /// + /// Method to determine if the command can be executed + /// + public bool CanExecute(T1 parameter1) + { + if (_canExecuteMethod != null) + { + return _canExecuteMethod(parameter1); + } + return true; + } + + /// + /// Execution of the command + /// + public void Execute(T1 parameter1) + { + if (_executeMethod != null) + { + _executeMethod(parameter1); + } + } + + /// + /// Raises the CanExecuteChaged event + /// + public void RaiseCanExecuteChanged() + { + OnCanExecuteChanged(); + } + + /// + /// Protected virtual method to raise CanExecuteChanged event + /// + protected virtual void OnCanExecuteChanged() + { + CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); + } + + /// + /// Property to enable or disable CommandManager's automatic requery on this command + /// + public bool IsAutomaticRequeryDisabled + { + get + { + return _isAutomaticRequeryDisabled; + } + set + { + if (_isAutomaticRequeryDisabled != value) + { + if (value) + { + CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); + } + else + { + CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); + } + _isAutomaticRequeryDisabled = value; + } + } + } + + /// + /// + /// + public object Target + { + get + { + if (_executeMethod == null) + return null; + + return _executeMethod.Target; + } + } + + #endregion + + #region ICommand Members + + /// + /// ICommand.CanExecuteChanged implementation + /// + public event EventHandler CanExecuteChanged + { + add + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested += value; + } + CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); + } + remove + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested -= value; + } + CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); + } + } + + public virtual bool CanExecute(object parameter) + { + if (parameter == null || (parameter is object[] && (parameter as object[]).Length < 1)) + return false; + + if (parameter is object[]) + { + object[] parameters = parameter as object[]; + + // if T is of value type and the parameter is not + // set yet, then return false if CanExecute delegate + // exists, else return true + if ((parameters[0] != null && !typeof(T1).IsAssignableFrom(parameters[0].GetType())) + || (parameters[0] == null && typeof(T1).IsValueType) + ) + { + return false; + } + + return CanExecute((T1)parameters[0]); + } + else + { + if (parameter != null && !typeof(T1).IsAssignableFrom(parameter.GetType())) + parameter = null; + + return CanExecute((T1)parameter); + } + } + + public virtual void Execute(object parameter) + { + //如果T1不允许为空,而parameter参数又为空,则直接返回 + if (typeof(T1).IsValueType && (parameter == null || (parameter is object[] && (parameter as object[]).Length < 1))) + return; + + if (parameter is object[]) + { + object[] parameters = parameter as object[]; + + // if T1 is of value type and the parameter is not + // set yet, then return false if CanExecute delegate + // exists, else return true + if ((parameters[0] != null && !typeof(T1).IsAssignableFrom(parameters[0].GetType())) + || (parameters[0] == null && typeof(T1).IsValueType) + ) + return; + + Execute((T1)parameters[0]); + } + else + { + if (parameter != null && !typeof(T1).IsAssignableFrom(parameter.GetType())) + parameter = null; + + Execute((T1)parameter); + } + } + + #endregion + + #region Data + + private readonly Action _executeMethod = null; + private readonly Func _canExecuteMethod = null; + private bool _isAutomaticRequeryDisabled = false; + private List _canExecuteChangedHandlers; + + #endregion + } + + public class CanExecuteDelegateCommand : IDelegateCommand + { + #region Constructors + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod) + : this(executeMethod, null, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod) + : this(executeMethod, canExecuteMethod, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled) + { + if (executeMethod == null) + { + throw new ArgumentNullException("executeMethod"); + } + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; + } + + #endregion + + #region Public Methods + + /// + /// Method to determine if the command can be executed + /// + public bool CanExecute(T1 parameter1, T2 parameter2) + { + if (_canExecuteMethod != null) + { + return _canExecuteMethod(parameter1, parameter2); + } + return true; + } + + /// + /// Execution of the command + /// + public void Execute(T1 parameter1, T2 parameter2) + { + if (_executeMethod != null) + { + _executeMethod(parameter1, parameter2); + } + } + + /// + /// Raises the CanExecuteChaged event + /// + public void RaiseCanExecuteChanged() + { + OnCanExecuteChanged(); + } + + /// + /// Protected virtual method to raise CanExecuteChanged event + /// + protected virtual void OnCanExecuteChanged() + { + CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); + } + + /// + /// Property to enable or disable CommandManager's automatic requery on this command + /// + public bool IsAutomaticRequeryDisabled + { + get + { + return _isAutomaticRequeryDisabled; + } + set + { + if (_isAutomaticRequeryDisabled != value) + { + if (value) + { + CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); + } + else + { + CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); + } + _isAutomaticRequeryDisabled = value; + } + } + } + + /// + /// + /// + public object Target + { + get + { + if (_executeMethod == null) + return null; + + return _executeMethod.Target; + } + } + + #endregion + + #region ICommand Members + + /// + /// ICommand.CanExecuteChanged implementation + /// + public event EventHandler CanExecuteChanged + { + add + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested += value; + } + CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); + } + remove + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested -= value; + } + CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); + } + } + + public virtual bool CanExecute(object parameter) + { + if (parameter == null || !(parameter is object[]) || (parameter as object[]).Length < 2) + return false; + + object[] parameters = parameter as object[]; + + // if T is of value type and the parameter is not + // set yet, then return false if CanExecute delegate + // exists, else return true + if ((parameters[0] != null && !typeof(T1).IsAssignableFrom(parameters[0].GetType())) + || (parameters[0] == null && typeof(T1).IsValueType) + || (parameters[1] != null && !typeof(T2).IsAssignableFrom(parameters[1].GetType())) + || (parameters[1] == null && typeof(T2).IsValueType) + ) + { + return false; + } + + return CanExecute((T1)parameters[0], (T2)parameters[1]); + } + + public virtual void Execute(object parameter) + { + if (parameter == null || !(parameter is object[]) || (parameter as object[]).Length < 2) + return; + + object[] parameters = parameter as object[]; + + // if T is of value type and the parameter is not + // set yet, then return false if CanExecute delegate + // exists, else return true + if ((parameters[0] != null && !typeof(T1).IsAssignableFrom(parameters[0].GetType())) + || (parameters[0] == null && typeof(T1).IsValueType) + || (parameters[1] != null && !typeof(T2).IsAssignableFrom(parameters[1].GetType())) + || (parameters[1] == null && typeof(T2).IsValueType) + ) + return; + + Execute((T1)parameters[0], (T2)parameters[1]); + } + + #endregion + + #region Data + + private readonly Action _executeMethod = null; + private readonly Func _canExecuteMethod = null; + private bool _isAutomaticRequeryDisabled = false; + private List _canExecuteChangedHandlers; + + #endregion + } + + public class CanExecuteDelegateCommand : IDelegateCommand + { + #region Constructors + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod) + : this(executeMethod, null, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod) + : this(executeMethod, canExecuteMethod, false) + { + } + + /// + /// Constructor + /// + public CanExecuteDelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled) + { + if (executeMethod == null) + { + throw new ArgumentNullException("executeMethod"); + } + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; + } + + #endregion + + #region Public Methods + + /// + /// Method to determine if the command can be executed + /// + public bool CanExecute(T1 parameter1, T2 parameter2, T3 parameter3) + { + if (_canExecuteMethod != null) + { + return _canExecuteMethod(parameter1, parameter2, parameter3); + } + return true; + } + + /// + /// Execution of the command + /// + public void Execute(T1 parameter1, T2 parameter2, T3 parameter3) + { + if (_executeMethod != null) + { + _executeMethod(parameter1, parameter2, parameter3); + } + } + + /// + /// Raises the CanExecuteChaged event + /// + public void RaiseCanExecuteChanged() + { + OnCanExecuteChanged(); + } + + /// + /// Protected virtual method to raise CanExecuteChanged event + /// + protected virtual void OnCanExecuteChanged() + { + CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); + } + + /// + /// Property to enable or disable CommandManager's automatic requery on this command + /// + public bool IsAutomaticRequeryDisabled + { + get + { + return _isAutomaticRequeryDisabled; + } + set + { + if (_isAutomaticRequeryDisabled != value) + { + if (value) + { + CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); + } + else + { + CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); + } + _isAutomaticRequeryDisabled = value; + } + } + } + + /// + /// + /// + public object Target + { + get + { + if (_executeMethod == null) + return null; + + return _executeMethod.Target; + } + } + + #endregion + + #region ICommand Members + + /// + /// ICommand.CanExecuteChanged implementation + /// + public event EventHandler CanExecuteChanged + { + add + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested += value; + } + CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); + } + remove + { + if (!_isAutomaticRequeryDisabled) + { + CommandManager.RequerySuggested -= value; + } + CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); + } + } + + public virtual bool CanExecute(object parameter) + { + if (parameter == null || !(parameter is object[]) || (parameter as object[]).Length < 3) + return false; + + object[] parameters = parameter as object[]; + + // if T is of value type and the parameter is not + // set yet, then return false if CanExecute delegate + // exists, else return true + if ((parameters[0] != null && !typeof(T1).IsAssignableFrom(parameters[0].GetType())) + || (parameters[0] == null && typeof(T1).IsValueType) + || (parameters[1] != null && !typeof(T2).IsAssignableFrom(parameters[1].GetType())) + || (parameters[1] == null && typeof(T2).IsValueType) + || (parameters[2] != null && !typeof(T3).IsAssignableFrom(parameters[2].GetType())) + || (parameters[2] == null && typeof(T3).IsValueType) + ) + { + return false; + } + + return CanExecute((T1)parameters[0], (T2)parameters[1], (T3)parameters[2]); + } + + public virtual void Execute(object parameter) + { + if (parameter == null || !(parameter is object[]) || (parameter as object[]).Length < 3) + return; + + object[] parameters = parameter as object[]; + + // if T is of value type and the parameter is not + // set yet, then return false if CanExecute delegate + // exists, else return true + if ((parameters[0] != null && !typeof(T1).IsAssignableFrom(parameters[0].GetType())) + || (parameters[0] == null && typeof(T1).IsValueType) + || (parameters[1] != null && !typeof(T2).IsAssignableFrom(parameters[1].GetType())) + || (parameters[1] == null && typeof(T2).IsValueType) + || (parameters[2] != null && !typeof(T3).IsAssignableFrom(parameters[2].GetType())) + || (parameters[2] == null && typeof(T3).IsValueType) + ) + return; + + Execute((T1)parameters[0], (T2)parameters[1], (T3)parameters[2]); + } + + #endregion + + #region Data + + private readonly Action _executeMethod = null; + private readonly Func _canExecuteMethod = null; + private bool _isAutomaticRequeryDisabled = false; + private List _canExecuteChangedHandlers; + + #endregion + } + + public interface IDelegateCommand : ICommand + { + object Target { get; } + } + + /// + /// This class contains methods for the CommandManager that help avoid memory leaks by + /// using weak references. + /// + internal class CommandManagerHelper + { + internal static void CallWeakReferenceHandlers(List handlers) + { + if (handlers != null) + { + // Take a snapshot of the handlers before we call out to them since the handlers + // could cause the array to me modified while we are reading it. + + EventHandler[] callees = new EventHandler[handlers.Count]; + int count = 0; + + for (int i = handlers.Count - 1; i >= 0; i--) + { + WeakReference reference = handlers[i]; + EventHandler handler = reference.Target as EventHandler; + if (handler == null) + { + // Clean up old handlers that have been collected + handlers.RemoveAt(i); + } + else + { + callees[count] = handler; + count++; + } + } + + // Call the handlers that we snapshotted + for (int i = 0; i < count; i++) + { + EventHandler handler = callees[i]; + handler(null, EventArgs.Empty); + } + } + } + + internal static void AddHandlersToRequerySuggested(List handlers) + { + if (handlers != null) + { + foreach (WeakReference handlerRef in handlers) + { + EventHandler handler = handlerRef.Target as EventHandler; + if (handler != null) + { + CommandManager.RequerySuggested += handler; + } + } + } + } + + internal static void RemoveHandlersFromRequerySuggested(List handlers) + { + if (handlers != null) + { + foreach (WeakReference handlerRef in handlers) + { + EventHandler handler = handlerRef.Target as EventHandler; + if (handler != null) + { + CommandManager.RequerySuggested -= handler; + } + } + } + } + + internal static void AddWeakReferenceHandler(ref List handlers, EventHandler handler) + { + AddWeakReferenceHandler(ref handlers, handler, -1); + } + + internal static void AddWeakReferenceHandler(ref List handlers, EventHandler handler, int defaultListSize) + { + if (handlers == null) + { + handlers = (defaultListSize > 0 ? new List(defaultListSize) : new List()); + } + + handlers.Add(new WeakReference(handler)); + } + + internal static void RemoveWeakReferenceHandler(List handlers, EventHandler handler) + { + if (handlers != null) + { + for (int i = handlers.Count - 1; i >= 0; i--) + { + WeakReference reference = handlers[i]; + EventHandler existingHandler = reference.Target as EventHandler; + if ((existingHandler == null) || (existingHandler == handler)) + { + // Clean up old handlers that have been collected + // in addition to the handler that is to be removed. + handlers.RemoveAt(i); + } + } + } + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/CommandReference.cs b/AIStudio.Wpf.ADiagram/Commands/CommandReference.cs new file mode 100644 index 0000000..8624246 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/CommandReference.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + public class CommandReference : Freezable, ICommand + { + public CommandReference() + { + // Blank + } + + public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged))); + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + #region ICommand Members + + public bool CanExecute(object parameter) + { + if (Command != null) + return Command.CanExecute(parameter); + return false; + } + + public void Execute(object parameter) + { + Command.Execute(parameter); + } + + public event EventHandler CanExecuteChanged; + + private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CommandReference commandReference = d as CommandReference; + ICommand oldCommand = e.OldValue as ICommand; + ICommand newCommand = e.NewValue as ICommand; + + if (oldCommand != null) + { + oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged; + } + if (newCommand != null) + { + newCommand.CanExecuteChanged += commandReference.CanExecuteChanged; + } + } + + #endregion + + #region Freezable + + protected override Freezable CreateInstanceCore() + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/CompositeCommand.cs b/AIStudio.Wpf.ADiagram/Commands/CompositeCommand.cs new file mode 100644 index 0000000..a79f5e8 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/CompositeCommand.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Windows.Input; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// The CompositeCommand composes one or more ICommands. + /// + public class CompositeCommand : ICommand + { + private readonly List _registeredCommands = new List(); + private readonly bool _monitorCommandActivity; + private readonly EventHandler _onRegisteredCommandCanExecuteChangedHandler; + private SynchronizationContext _synchronizationContext; + + /// + /// Initializes a new instance of . + /// + public CompositeCommand() + { + this._onRegisteredCommandCanExecuteChangedHandler = new EventHandler(this.OnRegisteredCommandCanExecuteChanged); + _synchronizationContext = SynchronizationContext.Current; + } + + /// + /// Initializes a new instance of . + /// + /// Indicates when the command activity is going to be monitored. + public CompositeCommand(bool monitorCommandActivity) + : this() + { + this._monitorCommandActivity = monitorCommandActivity; + } + + /// + /// Adds a command to the collection and signs up for the event of it. + /// + /// + /// If this command is set to monitor command activity, and + /// implements the interface, this method will subscribe to its + /// event. + /// + /// The command to register. + public virtual void RegisterCommand(ICommand command) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + if (command == this) + { + throw new ArgumentException("CannotRegisterCompositeCommandInItself"); + } + + lock (this._registeredCommands) + { + if (this._registeredCommands.Contains(command)) + { + throw new InvalidOperationException("CannotRegisterSameCommandTwice"); + } + this._registeredCommands.Add(command); + } + + command.CanExecuteChanged += this._onRegisteredCommandCanExecuteChangedHandler; + this.OnCanExecuteChanged(); + + if (this._monitorCommandActivity) + { + var activeAwareCommand = command as IActiveAware; + if (activeAwareCommand != null) + { + activeAwareCommand.IsActiveChanged += this.Command_IsActiveChanged; + } + } + } + + /// + /// Removes a command from the collection and removes itself from the event of it. + /// + /// The command to unregister. + public virtual void UnregisterCommand(ICommand command) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + bool removed; + lock (this._registeredCommands) + { + removed = this._registeredCommands.Remove(command); + } + + if (removed) + { + command.CanExecuteChanged -= this._onRegisteredCommandCanExecuteChangedHandler; + this.OnCanExecuteChanged(); + + if (this._monitorCommandActivity) + { + var activeAwareCommand = command as IActiveAware; + if (activeAwareCommand != null) + { + activeAwareCommand.IsActiveChanged -= this.Command_IsActiveChanged; + } + } + } + } + + private void OnRegisteredCommandCanExecuteChanged(object sender, EventArgs e) + { + this.OnCanExecuteChanged(); + } + + + /// + /// Forwards to the registered commands and returns + /// if all of the commands return . + /// + /// Data used by the command. + /// If the command does not require data to be passed, this object can be set to . + /// + /// if all of the commands return ; otherwise, . + public virtual bool CanExecute(object parameter) + { + bool hasEnabledCommandsThatShouldBeExecuted = false; + + ICommand[] commandList; + lock (this._registeredCommands) + { + commandList = this._registeredCommands.ToArray(); + } + foreach (ICommand command in commandList) + { + if (this.ShouldExecute(command)) + { + if (!command.CanExecute(parameter)) + { + return false; + } + + hasEnabledCommandsThatShouldBeExecuted = true; + } + } + + return hasEnabledCommandsThatShouldBeExecuted; + } + + /// + /// Occurs when any of the registered commands raise . + /// + public virtual event EventHandler CanExecuteChanged; + + /// + /// Forwards to the registered commands. + /// + /// Data used by the command. + /// If the command does not require data to be passed, this object can be set to . + /// + public virtual void Execute(object parameter) + { + Queue commands; + lock (this._registeredCommands) + { + commands = new Queue(this._registeredCommands.Where(this.ShouldExecute).ToList()); + } + + while (commands.Count > 0) + { + ICommand command = commands.Dequeue(); + command.Execute(parameter); + } + } + + /// + /// Evaluates if a command should execute. + /// + /// The command to evaluate. + /// A value indicating whether the command should be used + /// when evaluating and . + /// + /// If this command is set to monitor command activity, and + /// implements the interface, + /// this method will return if the command's + /// property is ; otherwise it always returns . + protected virtual bool ShouldExecute(ICommand command) + { + var activeAwareCommand = command as IActiveAware; + + if (this._monitorCommandActivity && activeAwareCommand != null) + { + return activeAwareCommand.IsActive; + } + + return true; + } + + /// + /// Gets the list of all the registered commands. + /// + /// A list of registered commands. + /// This returns a copy of the commands subscribed to the CompositeCommand. + public IList RegisteredCommands + { + get + { + IList commandList; + lock (this._registeredCommands) + { + commandList = this._registeredCommands.ToList(); + } + + return commandList; + } + } + + /// + /// Raises on the UI thread so every + /// command invoker can requery to check if the + /// can execute. + /// a + protected virtual void OnCanExecuteChanged() + { + var handler = CanExecuteChanged; + if (handler != null) + { + if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current) + _synchronizationContext.Post((o) => handler.Invoke(this, EventArgs.Empty), null); + else + handler.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Handler for IsActiveChanged events of registered commands. + /// + /// The sender. + /// EventArgs to pass to the event. + private void Command_IsActiveChanged(object sender, EventArgs e) + { + this.OnCanExecuteChanged(); + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/ControlBinding.cs b/AIStudio.Wpf.ADiagram/Commands/ControlBinding.cs new file mode 100644 index 0000000..3339400 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/ControlBinding.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Markup; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + [MarkupExtensionReturnType(typeof(object))] + public class ControlBinding : MarkupExtension + { + /// + /// 是否禁止处理绑定 + /// + public static bool Disabled = false; + + #region Properties + + public IValueConverter Converter { get; set; } + public object ConverterParameter { get; set; } + public string ElementName { get; set; } + public RelativeSource RelativeSource { get; set; } + public object Source { get; set; } + public bool ValidatesOnDataErrors { get; set; } + public bool ValidatesOnExceptions { get; set; } + public bool NotifyOnValidationError { get; set; } + public object TargetNullValue { get; set; } + public object FallBackValue { get; set; } + public string StringFormat { get; set; } + public UpdateSourceTrigger UpdateSourceTrigger { get; set; } + public Collection ValidationRules { get; set; } + + public BindingMode Mode { get; set; } + private int AncestorLevel = 1; + [ConstructorArgument("path")] + public PropertyPath Path { get; set; } + [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] + public CultureInfo ConverterCulture { get; set; } + + #endregion + + public ControlBinding() + { + Mode = BindingMode.Default; + ValidationRules = new Collection(); + } + + public ControlBinding(PropertyPath path) + { + Path = path; + Mode = BindingMode.Default; + UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; + NotifyOnValidationError = true; + ValidatesOnDataErrors = true; + ValidatesOnExceptions = true; + TargetNullValue = null; + ValidationRules = new Collection(); + } + + public ControlBinding(PropertyPath path, string elementName) + : this(path) + { + Path = path; + + if (elementName == "$") + AncestorLevel = 2; + else + ElementName = elementName; + } + + /// + /// 获取代理Binding对象 + /// + /// + internal Binding GetBinding(object rootObject) + { + Binding binding = new Binding(); + binding.StringFormat = StringFormat; + binding.Path = Path; + binding.Mode = Mode; + binding.Converter = Converter; + binding.ConverterCulture = ConverterCulture; + binding.ConverterParameter = ConverterParameter; + binding.UpdateSourceTrigger = UpdateSourceTrigger; + if (TargetNullValue != null) binding.TargetNullValue = TargetNullValue; + if (FallBackValue != null) + binding.FallbackValue = FallBackValue; + //单向绑定不需要进行数据校验 + NotifyOnValidationError &= (Mode != BindingMode.OneWay && Mode != BindingMode.OneTime); + + binding.ValidatesOnDataErrors = NotifyOnValidationError; + binding.ValidatesOnExceptions = NotifyOnValidationError; + binding.NotifyOnValidationError = NotifyOnValidationError; + + + //添加ValidationRule + foreach (ValidationRule vr in ValidationRules) + binding.ValidationRules.Add(vr); + + if (ElementName != null) + binding.ElementName = ElementName; + if (Source != null) + { + if (!(Source is string) || (Source as string) != "$") + binding.Source = Source; + } + if (RelativeSource != null) + binding.RelativeSource = RelativeSource; + + if (string.IsNullOrEmpty(ElementName) && Source == null && RelativeSource == null) + { + if (rootObject == null) + { + RelativeSource = new RelativeSource { AncestorLevel = AncestorLevel, AncestorType = typeof(UserControl), Mode = RelativeSourceMode.FindAncestor }; + + binding.RelativeSource = RelativeSource; + } + else + { + binding.Source = rootObject; + if (rootObject is UserControl || rootObject is Window) + { + if (binding.Path == null || binding.Path.Path == ".") + binding.Path = new PropertyPath("DataContext"); + else + binding.Path = new PropertyPath("DataContext." + binding.Path.Path, binding.Path.PathParameters); + } + } + } + + return binding; + } + + public override object ProvideValue(System.IServiceProvider serviceProvider) + { + var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; + if (valueProvider == null) + return null; + + + //当使用ControlMultiBinding或者MultiBinding时 + if (valueProvider.TargetProperty == null && valueProvider.TargetObject is ICollection) + { + if (valueProvider.TargetObject is Collection + || valueProvider.TargetObject.GetType().FullName == "MS.Internal.Data.BindingCollection") + return this; + } + + //如果禁用 + if (ControlBinding.Disabled) + return (valueProvider.TargetProperty as DependencyProperty).DefaultMetadata.DefaultValue; + + var rootObjProvider = serviceProvider.GetService(typeof(System.Xaml.IRootObjectProvider)) as System.Xaml.IRootObjectProvider; + + #region 如果是针对WinForm的ControlBinding + + if (valueProvider.TargetObject is System.Windows.Forms.Control && rootObjProvider != null) + { + System.Windows.Forms.Control ctl = valueProvider.TargetObject as System.Windows.Forms.Control; + System.Windows.Forms.Binding b = new System.Windows.Forms.Binding((valueProvider.TargetProperty as PropertyInfo).Name, rootObjProvider.RootObject, Path.Path); + if (Mode == BindingMode.OneWay) + { + b.ControlUpdateMode = System.Windows.Forms.ControlUpdateMode.OnPropertyChanged; + b.DataSourceUpdateMode = System.Windows.Forms.DataSourceUpdateMode.Never; + } + else if (Mode == BindingMode.OneWayToSource) + { + b.ControlUpdateMode = System.Windows.Forms.ControlUpdateMode.Never; + if (UpdateSourceTrigger == System.Windows.Data.UpdateSourceTrigger.PropertyChanged + || UpdateSourceTrigger == System.Windows.Data.UpdateSourceTrigger.Default) + b.DataSourceUpdateMode = System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged; + else if (UpdateSourceTrigger == System.Windows.Data.UpdateSourceTrigger.LostFocus) + b.DataSourceUpdateMode = System.Windows.Forms.DataSourceUpdateMode.OnValidation; + } + else if (Mode == BindingMode.TwoWay || Mode == BindingMode.Default) + { + b.ControlUpdateMode = System.Windows.Forms.ControlUpdateMode.OnPropertyChanged; + b.DataSourceUpdateMode = System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged; + } + else if (Mode == BindingMode.OneTime) + { + //这里应该赋值一次:OneTime + //...... + + b.ControlUpdateMode = System.Windows.Forms.ControlUpdateMode.Never; + b.DataSourceUpdateMode = System.Windows.Forms.DataSourceUpdateMode.Never; + } + + ctl.DataBindings.Add(b); + + return null; + } + + #endregion + + var bindingTarget = valueProvider.TargetObject as DependencyObject; + var bindingProperty = valueProvider.TargetProperty as DependencyProperty; + if (bindingProperty == null) + return null; + + #region 使用代理Binding返回最终的值 + + //得到Binding对象 + Binding binding = GetBinding(rootObjProvider == null ? null : rootObjProvider.RootObject); + if (binding == null) return null; + + object retValue = binding.ProvideValue(serviceProvider); + + #endregion + + return retValue; + } + } + + [ContentProperty("Bindings")] + public class ControlMultiBinding : MarkupExtension + { + #region Properties + + public Collection Bindings { get; private set; } + + public IMultiValueConverter Converter { get; set; } + + [DefaultValue("")] + [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] + public CultureInfo ConverterCulture { get; set; } + + [DefaultValue("")] + public object ConverterParameter { get; set; } + + public BindingMode Mode { get; set; } + + [DefaultValue(false)] + public bool NotifyOnSourceUpdated { get; set; } + + [DefaultValue(false)] + public bool NotifyOnTargetUpdated { get; set; } + + [DefaultValue(false)] + public bool NotifyOnValidationError { get; set; } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public UpdateSourceExceptionFilterCallback UpdateSourceExceptionFilter { get; set; } + + public UpdateSourceTrigger UpdateSourceTrigger { get; set; } + + [DefaultValue(false)] + public bool ValidatesOnDataErrors { get; set; } + + [DefaultValue(false)] + public bool ValidatesOnExceptions { get; set; } + + public Collection ValidationRules { get; private set; } + + #endregion + + public ControlMultiBinding() + { + Bindings = new Collection(); + ValidationRules = new Collection(); + Mode = BindingMode.Default; + UpdateSourceTrigger = System.Windows.Data.UpdateSourceTrigger.Default; + } + + /// + /// 获取代理MultiBindings对象 + /// + /// + internal MultiBinding GetMultiBindings(object rootObject) + { + MultiBinding multiBindings = new MultiBinding(); + + if (this.Converter != null) + multiBindings.Converter = this.Converter; + if (this.ConverterCulture != null) + multiBindings.ConverterCulture = this.ConverterCulture; + if (this.ConverterParameter != null) + multiBindings.ConverterParameter = this.ConverterParameter; + if (this.UpdateSourceExceptionFilter != null) + multiBindings.UpdateSourceExceptionFilter = this.UpdateSourceExceptionFilter; + multiBindings.Mode = this.Mode; + multiBindings.NotifyOnSourceUpdated = this.NotifyOnSourceUpdated; + multiBindings.NotifyOnTargetUpdated = this.NotifyOnTargetUpdated; + multiBindings.NotifyOnValidationError = this.NotifyOnValidationError; + multiBindings.UpdateSourceTrigger = this.UpdateSourceTrigger; + multiBindings.ValidatesOnDataErrors = this.ValidatesOnDataErrors; + multiBindings.ValidatesOnExceptions = this.ValidatesOnExceptions; + + foreach (object bd in Bindings) + { + if (bd is BindingBase) + multiBindings.Bindings.Add(bd as BindingBase); + else if (bd is ControlBinding) + multiBindings.Bindings.Add((bd as ControlBinding).GetBinding(rootObject) as BindingBase); + } + + foreach (ValidationRule vr in ValidationRules) + multiBindings.ValidationRules.Add(vr); + + return multiBindings; + } + + public override object ProvideValue(System.IServiceProvider serviceProvider) + { + var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; + if (valueProvider == null) + return null; + + var bindingTarget = valueProvider.TargetObject as DependencyObject; + var bindingProperty = valueProvider.TargetProperty as DependencyProperty; + if (bindingProperty == null) + return null; + + //使用MultiBindings返回最终的值 + #region 使用代理Binding返回最终的值 + + var rootObjProvider = serviceProvider.GetService(typeof(System.Xaml.IRootObjectProvider)) as System.Xaml.IRootObjectProvider; + + MultiBinding multiBindings = GetMultiBindings(rootObjProvider == null ? null : rootObjProvider.RootObject); + if (multiBindings == null) return null; + + object retValue = multiBindings.ProvideValue(serviceProvider); + + #endregion + + return retValue; + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/ControlCommand.cs b/AIStudio.Wpf.ADiagram/Commands/ControlCommand.cs new file mode 100644 index 0000000..288d7af --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/ControlCommand.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + #region ConbrolCommand + + public interface IControlCommand + { + void SetControl(Control cb); + + void SetBase(IControlCommand cc); + + void Execute(object parameter); + } + + /// + /// ControlBase可以使用的命令类 + /// 使用此类,可以使Command具有继承的机制,在新的Command中可以调用旧的Command + /// + public class ControlCommand : CanExecuteDelegateCommand, T1>, IControlCommand + { + public Control Control { get; protected set; } + + public IControlCommand Base { get; protected set; } + + public string Name { get; set; } + + public ControlCommand(Action, T1> executeMethod, Func, T1, bool> canExecuteMethod = null, bool isAutomaticRequeryDisabled = false, string name = "") + : base(executeMethod, canExecuteMethod, isAutomaticRequeryDisabled) + { + Name = name; + } + + public override bool CanExecute(object parameter) + { + return base.CanExecute(new object[] { this, parameter }); + } + + public override void Execute(object parameter) + { + base.Execute(new object[] { this, parameter }); + } + + public virtual void ExecuteBaseCommand(T1 parameter) + { + if (Base != null) + Base.Execute((object)parameter); + } + + public void SetControl(Control cb) + { + Control = cb; + } + + public void SetBase(IControlCommand cc) + { + Base = cc; + } + } + + public class ControlCommand : CanExecuteDelegateCommand, IControlCommand + { + public Control Control { get; protected set; } + + public IControlCommand Base { get; protected set; } + + public string Name { get; set; } + + public ControlCommand(Action executeMethod, Func canExecuteMethod = null, bool isAutomaticRequeryDisabled = false, string name = "") + : base(executeMethod, canExecuteMethod, isAutomaticRequeryDisabled) + { + Name = name; + } + + public override bool CanExecute(object parameter) + { + return base.CanExecute(new object[] { this }); + } + + public override void Execute(object parameter) + { + base.Execute(new object[] { this }); + } + + public virtual void ExecuteBaseCommand() + { + if (Base != null) + Base.Execute((object)null); + } + + public void SetControl(Control cb) + { + Control = cb; + } + + public void SetBase(IControlCommand cc) + { + Base = cc; + } + } + + #endregion +} diff --git a/AIStudio.Wpf.ADiagram/Commands/DelegateCommand(T).cs b/AIStudio.Wpf.ADiagram/Commands/DelegateCommand(T).cs new file mode 100644 index 0000000..ffb8f87 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/DelegateCommand(T).cs @@ -0,0 +1,140 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using System.Windows.Input; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// An whose delegates can be attached for and . + /// + /// Parameter type. + /// + /// The constructor deliberately prevents the use of value types. + /// Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings. + /// Using default(T) was considered and rejected as a solution because the implementor would not be able to distinguish between a valid and defaulted values. + /// + /// Instead, callers should support a value type by using a nullable value type and checking the HasValue property before using the Value property. + /// + /// + /// public MyClass() + /// { + /// this.submitCommand = new DelegateCommand<int?>(this.Submit, this.CanSubmit); + /// } + /// + /// private bool CanSubmit(int? customerId) + /// { + /// return (customerId.HasValue && customers.Contains(customerId.Value)); + /// } + /// + /// + /// + public class DelegateCommand : DelegateCommandBase + { + readonly Action _executeMethod; + Func _canExecuteMethod; + + /// + /// Initializes a new instance of . + /// + /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate. + /// will always return true. + public DelegateCommand(Action executeMethod) + : this(executeMethod, (o) => true) + { + } + + /// + /// Initializes a new instance of . + /// + /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate. + /// Delegate to execute when CanExecute is called on the command. This can be null. + /// When both and are . + public DelegateCommand(Action executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "DelegateCommandDelegatesCannotBeNull"); + + TypeInfo genericTypeInfo = typeof(T).GetTypeInfo(); + + // DelegateCommand allows object or Nullable<>. + // note: Nullable<> is a struct so we cannot use a class constraint. + if (genericTypeInfo.IsValueType) + { + if ((!genericTypeInfo.IsGenericType) || (!typeof(Nullable<>).GetTypeInfo().IsAssignableFrom(genericTypeInfo.GetGenericTypeDefinition().GetTypeInfo()))) + { + throw new InvalidCastException("DelegateCommandInvalidGenericPayloadType"); + } + } + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + ///Executes the command and invokes the provided during construction. + /// + ///Data used by the command. + public void Execute(T parameter) + { + _executeMethod(parameter); + } + + /// + ///Determines if the command can execute by invoked the provided during construction. + /// + ///Data used by the command to determine if it can execute. + /// + /// if this command can be executed; otherwise, . + /// + public bool CanExecute(T parameter) + { + return _canExecuteMethod(parameter); + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override void Execute(object parameter) + { + Execute((T)parameter); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + return CanExecute((T)parameter); + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The type of the return value of the method that this delegate encapulates + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + Expression> expression = System.Linq.Expressions.Expression.Lambda>(canExecuteExpression.Body, System.Linq.Expressions.Expression.Parameter(typeof(T), "o")); + _canExecuteMethod = expression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/DelegateCommand.cs b/AIStudio.Wpf.ADiagram/Commands/DelegateCommand.cs new file mode 100644 index 0000000..b5cc169 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/DelegateCommand.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// An whose delegates do not take any parameters for and . + /// + /// + /// + public class DelegateCommand : DelegateCommandBase + { + Action _executeMethod; + Func _canExecuteMethod; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public DelegateCommand(Action executeMethod) + : this(executeMethod, () => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The to invoke when is called + public DelegateCommand(Action executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "DelegateCommandDelegatesCannotBeNull"); + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Executes the command. + /// + public void Execute() + { + _executeMethod(); + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute() + { + return _canExecuteMethod(); + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override void Execute(object parameter) + { + Execute(); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + return CanExecute(); + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The object type containing the property specified in the expression. + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + _canExecuteMethod = canExecuteExpression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/DelegateCommandBase.cs b/AIStudio.Wpf.ADiagram/Commands/DelegateCommandBase.cs new file mode 100644 index 0000000..00f6758 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/DelegateCommandBase.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// An whose delegates can be attached for and . + /// + public abstract class DelegateCommandBase : ICommand, IActiveAware + { + private bool _isActive; + + private SynchronizationContext _synchronizationContext; + private readonly HashSet _observedPropertiesExpressions = new HashSet(); + + /// + /// Creates a new instance of a , specifying both the execute action and the can execute function. + /// + protected DelegateCommandBase() + { + _synchronizationContext = SynchronizationContext.Current; + } + + /// + /// Occurs when changes occur that affect whether or not the command should execute. + /// + public virtual event EventHandler CanExecuteChanged; + + /// + /// Raises so every + /// command invoker can requery . + /// + protected virtual void OnCanExecuteChanged() + { + var handler = CanExecuteChanged; + if (handler != null) + { + if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current) + _synchronizationContext.Post((o) => handler.Invoke(this, EventArgs.Empty), null); + else + handler.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Raises so every command invoker + /// can requery to check if the command can execute. + /// + /// Note that this will trigger the execution of once for each invoker. + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] + public void RaiseCanExecuteChanged() + { + OnCanExecuteChanged(); + } + + void ICommand.Execute(object parameter) + { + Execute(parameter); + } + + bool ICommand.CanExecute(object parameter) + { + return CanExecute(parameter); + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected abstract void Execute(object parameter); + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected abstract bool CanExecute(object parameter); + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The object type containing the property specified in the expression. + /// The property expression. Example: ObservesProperty(() => PropertyName). + protected internal void ObservesPropertyInternal(Expression> propertyExpression) + { + if (_observedPropertiesExpressions.Contains(propertyExpression.ToString())) + { + throw new ArgumentException($"{propertyExpression.ToString()} is already being observed.", + nameof(propertyExpression)); + } + else + { + _observedPropertiesExpressions.Add(propertyExpression.ToString()); + PropertyObserver.Observes(propertyExpression, RaiseCanExecuteChanged); + } + } + + #region IsActive + + /// + /// Gets or sets a value indicating whether the object is active. + /// + /// if the object is active; otherwise . + public bool IsActive + { + get { return _isActive; } + set + { + if (_isActive != value) + { + _isActive = value; + OnIsActiveChanged(); + } + } + } + + /// + /// Fired if the property changes. + /// + public virtual event EventHandler IsActiveChanged; + + /// + /// This raises the event. + /// + protected virtual void OnIsActiveChanged() + { + IsActiveChanged?.Invoke(this, EventArgs.Empty); + } + + #endregion + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/IActiveAware.cs b/AIStudio.Wpf.ADiagram/Commands/IActiveAware.cs new file mode 100644 index 0000000..fccab53 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/IActiveAware.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// Interface that defines if the object instance is active + /// and notifies when the activity changes. + /// + public interface IActiveAware + { + /// + /// Gets or sets a value indicating whether the object is active. + /// + /// if the object is active; otherwise . + bool IsActive { get; set; } + + /// + /// Notifies that the value for property has changed. + /// + event EventHandler IsActiveChanged; + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/PropertyObserver.cs b/AIStudio.Wpf.ADiagram/Commands/PropertyObserver.cs new file mode 100644 index 0000000..5a809c8 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/PropertyObserver.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// Provide a way to observe property changes of INotifyPropertyChanged objects and invokes a + /// custom action when the PropertyChanged event is fired. + /// + internal class PropertyObserver + { + private readonly Action _action; + + private PropertyObserver(System.Linq.Expressions.Expression propertyExpression, Action action) + { + _action = action; + SubscribeListeners(propertyExpression); + } + + private void SubscribeListeners(System.Linq.Expressions.Expression propertyExpression) + { + var propNameStack = new Stack(); + while (propertyExpression is MemberExpression temp) // Gets the root of the property chain. + { + propertyExpression = temp.Expression; + propNameStack.Push(temp.Member as PropertyInfo); // Records the member info as property info + } + + if (!(propertyExpression is ConstantExpression constantExpression)) + throw new NotSupportedException("Operation not supported for the given expression type. " + + "Only MemberExpression and ConstantExpression are currently supported."); + + var propObserverNodeRoot = new PropertyObserverNode(propNameStack.Pop(), _action); + PropertyObserverNode previousNode = propObserverNodeRoot; + foreach (var propName in propNameStack) // Create a node chain that corresponds to the property chain. + { + var currentNode = new PropertyObserverNode(propName, _action); + previousNode.Next = currentNode; + previousNode = currentNode; + } + + object propOwnerObject = constantExpression.Value; + + if (!(propOwnerObject is INotifyPropertyChanged inpcObject)) + throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " + + $"owns '{propObserverNodeRoot.PropertyInfo.Name}' property, but the object does not implements INotifyPropertyChanged."); + + propObserverNodeRoot.SubscribeListenerFor(inpcObject); + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls a custom action on + /// property changed notifications. The given expression must be in this form: "() => Prop.NestedProp.PropToObserve". + /// + /// Expression representing property to be observed. Ex.: "() => Prop.NestedProp.PropToObserve". + /// Action to be invoked when PropertyChanged event occours. + internal static PropertyObserver Observes(Expression> propertyExpression, Action action) + { + return new PropertyObserver(propertyExpression.Body, action); + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Commands/PropertyObserverNode.cs b/AIStudio.Wpf.ADiagram/Commands/PropertyObserverNode.cs new file mode 100644 index 0000000..b9bad70 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Commands/PropertyObserverNode.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace AIStudio.Wpf.ADiagram.Commands +{ + /// + /// Represents each node of nested properties expression and takes care of + /// subscribing/unsubscribing INotifyPropertyChanged.PropertyChanged listeners on it. + /// + internal class PropertyObserverNode + { + private readonly Action _action; + private INotifyPropertyChanged _inpcObject; + + public PropertyInfo PropertyInfo { get; } + public PropertyObserverNode Next { get; set; } + + public PropertyObserverNode(PropertyInfo propertyInfo, Action action) + { + PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); + _action = () => + { + action?.Invoke(); + if (Next == null) return; + Next.UnsubscribeListener(); + GenerateNextNode(); + }; + } + + public void SubscribeListenerFor(INotifyPropertyChanged inpcObject) + { + _inpcObject = inpcObject; + _inpcObject.PropertyChanged += OnPropertyChanged; + + if (Next != null) GenerateNextNode(); + } + + private void GenerateNextNode() + { + var nextProperty = PropertyInfo.GetValue(_inpcObject); + if (nextProperty == null) return; + if (!(nextProperty is INotifyPropertyChanged nextInpcObject)) + throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " + + $"owns '{Next.PropertyInfo.Name}' property, but the object does not implements INotifyPropertyChanged."); + + Next.SubscribeListenerFor(nextInpcObject); + } + + private void UnsubscribeListener() + { + if (_inpcObject != null) + _inpcObject.PropertyChanged -= OnPropertyChanged; + + Next?.UnsubscribeListener(); + } + + private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e?.PropertyName == PropertyInfo.Name || string.IsNullOrEmpty(e?.PropertyName)) + { + _action?.Invoke(); + } + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Controls/AnimationHelper.cs b/AIStudio.Wpf.ADiagram/Controls/AnimationHelper.cs new file mode 100644 index 0000000..ce82aae --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/AnimationHelper.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace AIStudio.Wpf.ADiagram.Controls +{ + /// + /// 包含一些常用的动画辅助方法 + /// + public class AnimationHelper + { + /// + /// 创建一个Thickness动画 + /// + /// + /// + /// + 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 } + }; + } + + /// + /// 创建一个Double动画 + /// + /// + /// + /// + 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; + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Controls/Barcode.xaml b/AIStudio.Wpf.ADiagram/Controls/Barcode.xaml new file mode 100644 index 0000000..ae8eeb8 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/Barcode.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/AIStudio.Wpf.ADiagram/Controls/Barcode.xaml.cs b/AIStudio.Wpf.ADiagram/Controls/Barcode.xaml.cs new file mode 100644 index 0000000..37753d0 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/Barcode.xaml.cs @@ -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.ADiagram.Controls +{ + /// + /// Barcode.xaml 的交互逻辑 + /// + 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; + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Controls/CancelRoutedEventArgs.cs b/AIStudio.Wpf.ADiagram/Controls/CancelRoutedEventArgs.cs new file mode 100644 index 0000000..43fe5be --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/CancelRoutedEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace AIStudio.Wpf.ADiagram.Controls +{ + public class CancelRoutedEventArgs : RoutedEventArgs + { + public CancelRoutedEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) + { + } + + public bool Cancel { get; set; } + } +} diff --git a/AIStudio.Wpf.ADiagram/Controls/ContextMenuToggleButton.cs b/AIStudio.Wpf.ADiagram/Controls/ContextMenuToggleButton.cs new file mode 100644 index 0000000..608c174 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/ContextMenuToggleButton.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; + +namespace AIStudio.Wpf.ADiagram.Controls +{ + /// + /// 带上下文菜单的切换按钮 + /// + public class ContextMenuToggleButton : ToggleButton + { + public ContextMenu Menu { get; set; } + + protected override void OnClick() + { + base.OnClick(); + if (Menu != null) + { + if (IsChecked == true) + { + Menu.PlacementTarget = this; + Menu.IsOpen = true; + } + else + { + Menu.IsOpen = false; + } + } + } + } +} diff --git a/AIStudio.Wpf.ADiagram/Controls/GradientStopControl.xaml b/AIStudio.Wpf.ADiagram/Controls/GradientStopControl.xaml new file mode 100644 index 0000000..2f0c307 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/GradientStopControl.xaml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIStudio.Wpf.ADiagram/Controls/GradientStopControl.xaml.cs b/AIStudio.Wpf.ADiagram/Controls/GradientStopControl.xaml.cs new file mode 100644 index 0000000..4cccee4 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/GradientStopControl.xaml.cs @@ -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 Util.DiagramDesigner; + +namespace AIStudio.Wpf.ADiagram.Controls +{ + /// + /// GradientStopControl.xaml 的交互逻辑 + /// + 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 Util.DiagramDesigner.GradientStop target) + { + ColorObject.SelectedGradientStop = target; + + } + } + } +} \ No newline at end of file diff --git a/AIStudio.Wpf.ADiagram/Controls/MultiSelectComboBox.xaml b/AIStudio.Wpf.ADiagram/Controls/MultiSelectComboBox.xaml new file mode 100644 index 0000000..b731f64 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/MultiSelectComboBox.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIStudio.Wpf.ADiagram/Controls/MultiSelectComboBox.xaml.cs b/AIStudio.Wpf.ADiagram/Controls/MultiSelectComboBox.xaml.cs new file mode 100644 index 0000000..345adc5 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/MultiSelectComboBox.xaml.cs @@ -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.ADiagram.Helpers; +using Util.DiagramDesigner; + +namespace AIStudio.Wpf.ADiagram.Controls +{ + /// + /// Interaction logic for MultiSelectComboBox.xaml + /// + public partial class MultiSelectComboBox : UserControl + { + private ObservableCollection _nodeList; + public MultiSelectComboBox() + { + InitializeComponent(); + _nodeList = new ObservableCollection(); + } + + #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)); + } + } + + } +} diff --git a/AIStudio.Wpf.ADiagram/Controls/OutlineText.cs b/AIStudio.Wpf.ADiagram/Controls/OutlineText.cs new file mode 100644 index 0000000..61a35d1 --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/OutlineText.cs @@ -0,0 +1,310 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; + +namespace AIStudio.Wpf.ADiagram.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 + } +} \ No newline at end of file diff --git a/AIStudio.Wpf.ADiagram/Controls/PopupWindow.xaml b/AIStudio.Wpf.ADiagram/Controls/PopupWindow.xaml new file mode 100644 index 0000000..011bb7b --- /dev/null +++ b/AIStudio.Wpf.ADiagram/Controls/PopupWindow.xaml @@ -0,0 +1,34 @@ + + + + + + + + + +