溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

基于WPF怎么制作一個可編程畫板

發(fā)布時間:2023-05-04 15:36:36 來源:億速云 閱讀:119 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“基于WPF怎么制作一個可編程畫板”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強(qiáng),希望這篇“基于WPF怎么制作一個可編程畫板”文章能幫助大家解決問題。

先上一張效果動圖

基于WPF怎么制作一個可編程畫板

簡單使用,自定義一個text模塊的代碼如下

Code = @"using System;
namespace AIStudio.Wpf.CSharpScript
{
    public class Writer
    {   
        public string StringValue{ get; set;} = ""Welcome to AIStudio.Wpf.Diagram"";
        public string Execute()
        {
            return StringValue;
        }
    }
}";

是不是很簡單。

本次擴(kuò)展的主要內(nèi)容

1.可編程模塊,使用C#語言。

2.控制臺打印控件,可以打印程序中的Console.WriteLine數(shù)據(jù)

3.為了便于大家使用,寫了一個Box工廠分配Box的數(shù)據(jù)流向效果圖。

可編程模塊的實現(xiàn)原理

使用Microsoft.CodeAnalysis.CSharp.Scripting對代碼進(jìn)行編譯,生成Assembly,然后對Assembly反射獲得對象,對象內(nèi)部固定有一個Execute方法,每次掃描的時候執(zhí)行即可。

1.編譯使用的Using,必須添加引用集,為了省事,把整個程序的Reference都放入進(jìn)行編譯,獲得引用的核心代碼如下:

var references = AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic && !string.IsNullOrEmpty(p.Location)).Select(x => MetadataReference.CreateFromFile(x.Location)).ToList();
//Costura.Fody壓縮后,無Location,讀取資源文件中的reference
foreach (var assemblyEmbedded in AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic && string.IsNullOrEmpty(p.Location)))
{
    using (var stream = Assembly.GetEntryAssembly().GetManifestResourceStream($"costura.{assemblyEmbedded.GetName().Name.ToLowerInvariant()}.dll.compressed"))
    {
        if (stream != null)
        {
            using (var compressStream = new DeflateStream(stream, CompressionMode.Decompress))
            {
                var memStream = new MemoryStream();
                CopyTo(compressStream, memStream);
                memStream.Position = 0;
                references.Add(MetadataReference.CreateFromStream(memStream));
            }
        }
    }
}

2.動態(tài)編譯的代碼的核心代碼如下:

public static Assembly GenerateAssemblyFromCode(string code, out string message)
{
    Assembly assembly = null;
    message = "";
    // 叢代碼中轉(zhuǎn)換表達(dá)式樹
    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
    // 隨機(jī)程序集名稱
    string assemblyName = Path.GetRandomFileName();
    // 引用
    // 創(chuàng)建編譯對象
    CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, References, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
    using (var ms = new MemoryStream())
    {
        // 將編譯好的IL代碼放入內(nèi)存流
        EmitResult result = compilation.Emit(ms);
        // 編譯失敗,提示
        if (!result.Success)
        {
            IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error).ToList();
            foreach (Diagnostic diagnostic in failures)
            {
                message += $"{diagnostic.Id}: {diagnostic.GetMessage()}";
                Console.WriteLine(message);
            }
        }
        else
        {
            // 編譯成功,從內(nèi)存中加載編譯好的程序集
            ms.Seek(0, SeekOrigin.Begin);
            assembly = Assembly.Load(ms.ToArray());
        }
    }
    return assembly;
}

3.獲得編譯后的程序集,以及執(zhí)行。

// 反射獲取程序集中 的類
Type type = assembly.GetTypes().FirstOrDefault(p => p.FullName.StartsWith("AIStudio.Wpf"));   //assembly.GetType("AIStudio.Wpf.CSharpScript.Write");
// 創(chuàng)建該類的實例
object obj = Activator.CreateInstance(type);
// 通過反射方式調(diào)用類中的方法。
var result = type.InvokeMember("Execute",
    BindingFlags.Default | BindingFlags.InvokeMethod,
    null,
    obj,
    new object[] { });

代碼編輯模塊的實現(xiàn)

選擇AvalonEdit控件,另外為了使用VS2019_Dark的黑色皮膚,引用官方Demo中的HL和TextEditlib實現(xiàn)自定義換膚。 

基于WPF怎么制作一個可編程畫板

 官方Demo的換膚寫的超級復(fù)雜,看不懂,但是我們只要理解換膚的核心部分就是動態(tài)資源字典,因此我簡化下,改進(jìn)后的核心換膚代碼如下:

public class TextEditorThemeHelper
{
    static Dictionary<string, ResourceDictionary> ThemeDictionary = new Dictionary<string, ResourceDictionary>();
    public static List<string> Themes = new List<string>() { "Dark", "Light", "TrueBlue", "VS2019_Dark" };
    public static string CurrentTheme { get; set; }
    static TextEditorThemeHelper()
    {
        var resource = new ResourceDictionary { Source = new Uri("/TextEditLib;component/Themes/LightBrushs.xaml", UriKind.RelativeOrAbsolute) };
        ThemeDictionary.Add("Light", resource);
        resource = new ResourceDictionary { Source = new Uri("/TextEditLib;component/Themes/DarkBrushs.xaml", UriKind.RelativeOrAbsolute) };
        ThemeDictionary.Add("Dark", resource);
        Application.Current.Resources.MergedDictionaries.Add(resource);
    }
    /// <summary>
    /// 設(shè)置主題
    /// </summary>
    /// <param name="theme"></param>
    public static void SetCurrentTheme(string theme)
    {
        OnAppThemeChanged(theme);//切換到VS2019_Dark
        CurrentTheme = theme;
    }
    /// <summary>
    /// Invoke this method to apply a change of theme to the content of the document
    /// (eg: Adjust the highlighting colors when changing from "Dark" to "Light"
    ///      WITH current text document loaded.)
    /// </summary>
    internal static void OnAppThemeChanged(string theme)
    {
        ThemedHighlightingManager.Instance.SetCurrentTheme(theme);
        if (ThemeDictionary.ContainsKey(theme))
        {
            foreach (var key in ThemeDictionary[theme].Keys)
            {
                ApplyToDynamicResource(key, ThemeDictionary[theme][key]);
            }
        }
        // Does this highlighting definition have an associated highlighting theme?
        else if (ThemedHighlightingManager.Instance.CurrentTheme.HlTheme != null)
        {
            // A highlighting theme with GlobalStyles?
            // Apply these styles to the resource keys of the editor
            foreach (var item in ThemedHighlightingManager.Instance.CurrentTheme.HlTheme.GlobalStyles)
            {
                switch (item.TypeName)
                {
                    case "DefaultStyle":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorBackground, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorForeground, item.foregroundcolor);
                        break;
                    case "CurrentLineBackground":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorCurrentLineBackgroundBrushKey, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorCurrentLineBorderBrushKey, item.bordercolor);
                        break;
                    case "LineNumbersForeground":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLineNumbersForeground, item.foregroundcolor);
                        break;
                    case "Selection":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorSelectionBrush, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorSelectionBorder, item.bordercolor);
                        break;
                    case "Hyperlink":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLinkTextBackgroundBrush, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLinkTextForegroundBrush, item.foregroundcolor);
                        break;
                    case "NonPrintableCharacter":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorNonPrintableCharacterBrush, item.foregroundcolor);
                        break;
                    default:
                        throw new System.ArgumentOutOfRangeException("GlobalStyle named '{0}' is not supported.", item.TypeName);
                }
            }
        }
    }
    /// <summary>
    /// Re-define an existing <seealso cref="SolidColorBrush"/> and backup the originial color
    /// as it was before the application of the custom coloring.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="newColor"></param>
    private static void ApplyToDynamicResource(ComponentResourceKey key, Color? newColor)
    {
        if (Application.Current.Resources[key] == null || newColor == null)
            return;
        // Re-coloring works with SolidColorBrushs linked as DynamicResource
        if (Application.Current.Resources[key] is SolidColorBrush)
        {
            //backupDynResources.Add(resourceName);
            var newColorBrush = new SolidColorBrush((Color)newColor);
            newColorBrush.Freeze();
            Application.Current.Resources[key] = newColorBrush;
        }
    }
    private static void ApplyToDynamicResource(object key, object newValue)
    {
        if (Application.Current.Resources[key] == null || newValue == null)
            return;
        Application.Current.Resources[key] = newValue;
    }
}

使用方法:

TextEditorThemeHelper.SetCurrentTheme("VS2019_Dark");

或者 TextEditorThemeHelper.SetCurrentTheme("TrueBlue");

或者 TextEditorThemeHelper.SetCurrentTheme("Dark");

或者 TextEditorThemeHelper.SetCurrentTheme("Light");

是不是超級簡單。

代碼編輯模塊的編譯與測試

基于WPF怎么制作一個可編程畫板

基于WPF怎么制作一個可編程畫板

WPF打印控制臺數(shù)據(jù)

///控制臺打印方法支持切換運(yùn)行輸出方法Console.SetOut,核心代碼如下:
public class ConsoleWriter : TextWriter
{
    private readonly Action<string> _Write;
    private readonly Action<string> _WriteLine;
    private readonly Action<string, string, string, int> _WriteCallerInfo;
    public ConsoleWriter()
    {
    }
    /// <summary>
    /// Console 輸出重定向
    /// </summary>
    /// <param name="write">日志方法委托(針對于 Write)</param>
    /// <param name="writeLine">日志方法委托(針對于 WriteLine)</param>
    public ConsoleWriter(Action<string> write, Action<string> writeLine, Action<string, string, string, int> writeCallerInfo)
    {
        _Write = write;
        _WriteLine = writeLine?? write;
        _WriteCallerInfo = writeCallerInfo;
    }
    /// <summary>
    /// Console 輸出重定向
    /// </summary>
    /// <param name="write">日志方法委托(針對于 Write)</param>
    /// <param name="writeLine">日志方法委托(針對于 WriteLine)</param>
    public ConsoleWriter(Action<string> write, Action<string> writeLine)
    {
        _Write = write;
        _WriteLine = writeLine;
    }
    /// <summary>
    /// Console 輸出重定向
    /// </summary>
    /// <param name="write">日志方法委托</param>
    public ConsoleWriter(Action<string> write)
    {
        _Write = write;
        _WriteLine = write;
    }
    /// <summary>
    /// Console 輸出重定向(帶調(diào)用方信息)
    /// </summary>
    /// <param name="write">日志方法委托(后三個參數(shù)為 CallerFilePath、CallerMemberName、CallerLineNumber)</param>
    public ConsoleWriter(Action<string, string, string, int> write)
    {
        _WriteCallerInfo = write;
    }
    /// <summary>
    /// 使用 UTF-16 避免不必要的編碼轉(zhuǎn)換
    /// </summary>
    public override Encoding Encoding => Encoding.Unicode;
    /// <summary>
    /// 最低限度需要重寫的方法
    /// </summary>
    /// <param name="value">消息</param>
    public override void Write(string value)
    {
        if (_WriteCallerInfo != null)
        {
            WriteWithCallerInfo(value);
            return;
        }
        _Write(value);
    }
    /// <summary>
    /// 為提高效率直接處理一行的輸出
    /// </summary>
    /// <param name="value">消息</param>
    public override void WriteLine(string value)
    {
        if (_WriteCallerInfo != null)
        {
            WriteWithCallerInfo(value);
            return;
        }
        _WriteLine(value);
    }
    /// <summary>
    /// 帶調(diào)用方信息進(jìn)行寫消息
    /// </summary>
    /// <param name="value">消息</param>
    private void WriteWithCallerInfo(string value)
    {
        //3、System.Console.WriteLine -> 2、System.IO.TextWriter + SyncTextWriter.WriteLine -> 1、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteLine -> 0、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteWithCallerInfo
        var callInfo = ClassHelper.GetMethodInfo(4);
        _WriteCallerInfo(value, callInfo?.FileName, callInfo?.MethodName, callInfo?.LineNumber ?? 0);
    }
    public override void Close()
    {
        var standardOutput = new StreamWriter(Console.OpenStandardOutput());
        standardOutput.AutoFlush = true;
        Console.SetOut(standardOutput);
        base.Close();
    }
}

使用:

ConsoleWriter ConsoleWriter = new ConsoleWriter(_write, _writeLine);

Console.SetOut(ConsoleWriter);

動態(tài)編譯模塊的輸入輸出自動生成

1.輸入輸出模塊:public string Value{ get; set;}

2.輸入模塊:public string Value{private get; set;}

3.輸出模塊:public string Value{get;private set;}

4.與外部交互模塊:private string Value{ get; set;} ,必須同名同屬性。 核心代碼如下:

public static Dictionary<string, List<PropertyInfo>> GetPropertyInfo(Type type)
{
    Dictionary<string, List<PropertyInfo>> puts = new Dictionary<string, List<PropertyInfo>>()
    {
        {"Input", new List<PropertyInfo>() },
        {"Output", new List<PropertyInfo>() },
        {"Input_Output", new List<PropertyInfo>() },
        {"Inner", new List<PropertyInfo>() }
    };
    try
    {
        foreach (System.Reflection.PropertyInfo info in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (info.CanRead && info.CanWrite)
            {
                if (info.SetMethod.IsPublic && info.GetMethod.IsPublic)
                {
                    puts["Input_Output"].Add(info);
                }
                else if (info.SetMethod.IsPublic)
                {
                    puts["Input"].Add(info);
                }
                else if (info.GetMethod.IsPublic)
                {
                    puts["Output"].Add(info);
                }
            }
            else if (info.CanRead)
            {
                if (info.GetMethod.IsPublic)
                {
                    puts["Output"].Add(info);
                }
            }
        }
        foreach (System.Reflection.PropertyInfo info in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance))
        {
            if (info.CanRead)
            {
                puts["Inner"].Add(info);
            }
        }
    }
    catch (Exception ex)
    {
    }
    return puts;
}

最后介紹一下Demo的實現(xiàn)

1#.Int整數(shù)模塊,界面定義一個TextBox綁定Int模塊的輸入管腳。 2#.Box產(chǎn)生模塊,如果內(nèi)部數(shù)組為空,那么按照輸入管腳的數(shù)量初始化一個容量為輸入整數(shù)數(shù)量的數(shù)組(隨機(jī)顏色與形狀),然后把數(shù)據(jù)放到輸出管腳,當(dāng)數(shù)據(jù)被取走后,下一個數(shù)據(jù)再次放到輸出管腳。 3#.Bool模塊,為false的時候按照顏色進(jìn)行分配,為true的時候按照形狀進(jìn)行分配。4#.Box分配模塊,當(dāng)輸入管腳為空的時候,2#模塊的輸出可以移動到4#的輸入管腳,移動時間為1s,移動完成后,清除2#模塊的輸出。同時把數(shù)據(jù)按照顏色或者形狀分配到輸出,同時把輸入管腳清除。 按照顏色分配時: (1.如果顏色為紅色,那么輸出到1號 (2.如果顏色為橙色,那么輸出到2號 (3.如果顏色為黃色,那么輸出到3號 (4.如果顏色為綠色,那么輸出到4號 (5.如果顏色為青色,那么輸出到5號 (6.如果顏色為藍(lán)色,那么輸出到6號 (7.如果顏色為紫色,那么輸出到7號 按照形狀分配時: (1.如果形狀為圓形,那么輸出到1號 (2.如果形狀為三角形,那么輸出到2號 (3.如果形狀為方形,那么輸出到3號 (4.如果形狀為菱形,那么輸出到4號 (5.如果形狀為梯形,那么輸出到5號 (6.如果形狀為五角星,那么輸出到6號 (7.如果形狀為六邊形,那么輸出到7號 6#.有兩個紅色|圓形收集器(7#,8#),按兩個容器中的數(shù)量比較反饋,均勻分配到這兩個收集器中。 9#,10#,11#,12#,13#,14#按照管腳取走數(shù)據(jù)即可。

關(guān)于“基于WPF怎么制作一個可編程畫板”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

wpf
AI