深入淺出WPF的命令系統

来源:https://www.cnblogs.com/tianlang358/archive/2023/01/30/17077102.html
-Advertisement-
Play Games

前言 相信大家看過不少講C# async await的文章,博客園就能搜到很多,但還是有很多C#程式員不明白。 如果搞不明白,其實也不影響使用。但有人就會疑惑,為什麼要用非同步?我感覺它更慢了,跟同步有啥區別? 有的人研究深入,比如去研究狀態機,可能會明白其中的原理。但深入研究的畢竟少數。有的人寫一些 ...


1.什麼是命令?

我們通過一個場景來說明這個問題。假設某天某個時間點,公司領導對小王說:“小王,去前臺幫我取一下快遞。”這裡,領導對小王所說的話就可以理解為命令,簡而言之,命令就是一段信息,那為什麼還要牽扯出“領導”和“小王”呢?那是因為他們是和命令相關的且不可或缺的部分,他們是產生命令(命令源)和處理命令(命令目標)的人。與之類似,WPF中的命令系統也有這些元素,WPF中的命令模型可以分解為四個主要概念:ICommand,ICommandSource,命令目標及CommandBinding。

ICommand

命令,表示要執行的操作。

WPF 中的命令是通過實現 ICommand 介面創建的。 ICommand 公開了兩種方法 Execute 和 CanExecute,以及一個事件 CanExecuteChanged。 Execute 執行與該命令關聯的操作。 CanExecute 確定是否可以在當前命令目標上執行該命令。如果集中管理命令操作的命令管理器檢測到命令源中存在一個可能使已引發命令無效但尚未由命令綁定執行的更改,則會引發 CanExecuteChanged。

ICommand在WPF中的預設實現是 RoutedCommand 和RoutedUICommand。

ICommandSource

命令源是產生命令或調用命令的對象。WPF中的命令源通常實現 ICommandSource介面,常見的有Button、MenuItem等。其定義如下:

 1 public interface ICommandSource
 2 {
 3     ICommand Command
 4     {
 5         get;
 6     }
 7 
 8     object CommandParameter
 9     {
10         get;
11     }
12 
13     IInputElement CommandTarget
14     {
15         get;
16     }
17 }

其中:

Command就是要執行的命令;

CommandParameter是用於將信息傳遞給命令執行程式的數據類;

CommandTarget即要在其上執行命令的命令目標,其必須是實現IInputElement的對象。

命令目標

在其上執行命令的對象。命令目標沒有命令源那樣有個顧名思義的約束(ICommandSource),但是命令目標必須實現IInputElement介面。

CommandBinding

是將命令邏輯映射到命令的對象。

CommandBinding公開了四個事件PreviewExecuted,Executed,PreviewCanExecute,CanExecute和兩個方法OnCanExecute,OnExecuted,其中OnCanExecute方法會觸發PreviewCanExecute和CanExecute事件,而OnExecuted方法則會觸發PreviewExecuted和Executed事件。

是WPF專門為RoutedCommand提供的。

2.命令的用途

簡單來說,命令有兩個用途:

1、將命令發出者和命令執行者分開,這樣做的好處是命令發出者可以將同一個命令傳遞給不同的命令執行者;

2、指示發出命令的操作是否可以執行。仍然是前面的場景,當領導發出命令後,小王認為自己沒空或者不想去跑腿,對領導說:“你給我閉嘴!”(小王家裡可能有礦),那麼命令就不會被執行,對應到WPF中,假定ButtonA關聯了命令A,那麼當該命令A不可執行時(由命令系統判定),ButtonA會表現為禁用狀態,即命令源無法發出命令。

3.如何使用命令?

以RoutedCommand為例。

Xaml中的實現

 1 <Window.Resources>
 2     <RoutedCommand x:Key="sampleCmd"/>
 3 </Window.Resources>
 4 <Window.CommandBindings>
 5     <CommandBinding Command="{StaticResource sampleCmd}" CanExecute="OnSampleCommandCanExecuted" Executed="OnSampleCommandExecuted"/>
 6 </Window.CommandBindings>
 7 <Grid>
 8     <Button x:Name="sampleButton"
 9             Content="sample button"
10             Command="{StaticResource sampleCmd}"/>
11 </Grid>

用C#來實現就是:

1 2 RoutedCommand sampleCmd = new RoutedCommand();
3 CommandBinding cmdBindng = new CommandBinding(sampleCmd, OnSampleCommandExecuted, OnSampleCommandCanExecuted);
4 CommandBindings.Add(cmdBindng);
5 sampleButton.Command = sampleCmd;
6
1 private void OnSampleCommandExecuted(object sender, ExecutedRoutedEventArgs e)
2 {
3     MessageBox.Show("Sample Button Clicked."); 
4 }
5 
6 private void OnSampleCommandCanExecuted(object sender, CanExecuteRoutedEventArgs e)
7 {
8     e.CanExecute = true;
9 }

4.命令是如何執行的?

下麵以Button控制項為例,來說明命令是如何執行的。我們知道,當Button被按下時,會調用Click方法,其實現如下:

1 protected virtual void OnClick()
2 {
3     RoutedEventArgs e = new RoutedEventArgs(ClickEvent, this);
4     RaiseEvent(e);
5     CommandHelpers.ExecuteCommandSource(this);
6 }

lick方法會先觸發ClickedEvent路由事件,接著才通過CommandHelpers工具類進入命令調用邏輯:

 1 internal static void ExecuteCommandSource(ICommandSource commandSource)
 2 {
 3  4     object commandParameter = commandSource.CommandParameter;
 5     IInputElement inputElement = commandSource.CommandTarget;
 6     RoutedCommand routedCommand = command as RoutedCommand;
 7     if (routedCommand != null)
 8     {
 9         if (inputElement == null)
10         {
11              inputElement = (commandSource as IInputElement);
12         }
13 
14         if (routedCommand.CanExecute(commandParameter, inputElement))
15         {
16             routedCommand.ExecuteCore(commandParameter, inputElement, userInitiated);
17         }
18     }
19     else if (command.CanExecute(commandParameter))
20     {
21         command.Execute(commandParameter);
22     }
23 }

在ExecuteCommandSource方法中,會首先判斷命令源發起的命令是否是RoutedCommand命令,如果不是,則直接調用ICommand介面的Execute方法,並將命令源的CommandParameter作為參數傳入該方法,簡單明瞭;而如果是RoutedCommand,下一步會指定命令目標,命令目標的指定邏輯如下:

Step1、如果傳入的命令源中的命令目標有效,則以該命令目標作為最終的命令目標;

Step2、如果命令源中的命令目標無效,則以命令源作為命令目標;

Step3、如果命令源不是合法的命令目標,則以當前獲得焦點的對象作為命令目標;

命令目標確定後,會在命令目標上先後觸發CommandManager.PreviewExecutedEvent和CommandManager.ExecutedEvent事件,具體代碼如下:

 1 internal bool ExecuteCore(object parameter, IInputElement target, bool userInitiated)
 2 {
 3     if (target == null)
 4     {
 5         target = FilterInputElement(Keyboard.FocusedElement);
 6     }
 7 
 8     return ExecuteImpl(parameter, target, userInitiated);
 9 }
10 
11 private bool ExecuteImpl(object parameter, IInputElement target, bool userInitiated)
12 {
13     if (target != null && !IsBlockedByRM)
14     {
15         UIElement uIElement = target as UIElement;
16         ContentElement contentElement = null;
17         UIElement3D uIElement3D = null;
18         ExecutedRoutedEventArgs executedRoutedEventArgs = new ExecutedRoutedEventArgs(this, parameter);
19         executedRoutedEventArgs.RoutedEvent = CommandManager.PreviewExecutedEvent;
20         if (uIElement != null)
21         {
22             uIElement.RaiseEvent(executedRoutedEventArgs, userInitiated);
23         }
24         else
25         {
26             ...
27         }
28 
29         if (!executedRoutedEventArgs.Handled)
30         {
31             executedRoutedEventArgs.RoutedEvent = CommandManager.ExecutedEvent;
32             if (uIElement != null)
33             {
34                 uIElement.RaiseEvent(executedRoutedEventArgs, userInitiated);
35             }
36             ...
37         }
38 
39         return executedRoutedEventArgs.Handled;
40     }
41 
42     return false;
43 }

從進入Button.OnClick開始就遇到了RaiseEvent,現在又遇到了,那這個方法到底做了什麼是呢?長話短說,RaiseEvent的職責是構建指定事件的路由路徑,並按照路由路徑執行此事件。構建方法就是從元素樹中查找某個元素(IInputElement)是否需要處理指定的路由事件(這裡就是CommandManager.PreviewExecutedEvent或CommandManager.ExecutedEvent事件),如果某個元素需要處理指定的路由事件,那麼這個元素將會被添加到路由路徑中去。

那是如何做到讓某個對象與某個路由事件關聯的呢?可以通過兩種方式:

方式一:調用IInputElement介面中的AddHandler(RoutedEvent routedEvent, Delegate handler)方法;

方式二:通過EventManager靜態工具類提供的路由事件註冊方法;

EventManager提供用於為類型映射路由事件處理器的方法,且經由EventManager映射的路由事件處理程式會在經由IInputElement.AddHandler方法映射的路由事件處理程式之前被調用,這是由IInputElement的實現類決定的,以UIElement為例,UIElement在構建事件的路由路徑時,會先去匹配經由EventManager工具類映射的路由事件處理程式,其次才去匹配經由IInputElement實例映射的。

事件的路由路徑構建好之後,RaiseEvent方法接下來會沿著路由路徑一一執行該事件的事件處理程式。到此,一個事件的觸發、執行便完成了。

命令成功執行!有沒有疑惑?有!

疑惑1.按照上述的命令使用方法,命令是通過CommandBinding與其處理程式進行關聯的,但如果去查看CommandBinding的實現,CommandBinding並沒有通過上述的兩個方式註冊路由事件的處理程式;另外,上述的代碼也沒有顯式的註冊CommandManager.PreviewExecutedEvent或CommandManager.ExecutedEvent事件,命令怎麼就被執行了呢?

疑惑2.即便某個對象隱式的註冊了上述兩個事件,那當事件被觸發時,也是應當通過調用CommandBinding公開的方法來執行事件處理程式才對,但並沒有發現UIElement中有調用CommandBinding的地方!

不急,接下來一 一解惑!

對於疑惑1,假定WPF仍然是通過路由事件的方式執行了命令,那麼在此之前提到了註冊路由事件的兩種方式,不妨在UIElement中查找一下是否有註冊過這兩個事件,哎,果然,在其RegisterEvents(Type type)方法中對這兩個事件進行了註冊:

 1 internal static void RegisterEvents(Type type)
 2 {
 3     ...
 4     EventManager.RegisterClassHandler(type, CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(OnPreviewExecutedThunk), handledEventsToo: false);
 5     EventManager.RegisterClassHandler(type, CommandManager.ExecutedEvent, new ExecutedRoutedEventHandler(OnExecutedThunk), handledEventsToo: false);
 6     ...
 7 }
 8 private static void OnExecutedThunk(object sender, ExecutedRoutedEventArgs e)
 9 {
10     ...
11     CommandManager.OnExecuted(sender, e);
12 }

好了,第一個疑點真相大白!即UIElement的靜態構造函數通過調用其RegisterEvents方法,為UIElement類型註冊了上述兩個事件,而Button是UIElement的派生類,自然也就適用了。

對於疑惑2,能想到的直接辦法就是看一下誰調用了CommandBinding公開的OnExecuted方法。通過ILSpy反編譯工具,可以看到

可以看到,UIElement.OnExecutedThunk方法通過一系列調用最終執行了CommandBinding.OnExecuted方法。

OnExecutedThunk方法看著有點眼熟,對了,在說明疑惑1的時候提到此方法是被作為事件處理器給註冊到了CommandManager.ExecutedEvent事件。哦,原來疑點2和疑點1是殊途同歸啊!

好了,到這裡對(路由)命令的執行過程應該有了一個清晰的瞭解。接下來說明下上文“命令用途”中提到的“指示發出命令的操作是否可以執行”,WPF命令系統是如何做到這一點的?是時候請出CommandManager了!

CommandManager

CommandManager提供了一組靜態方法,用於在特定元素中添加和移除PreviewExecuted、Executed、PreviewCanExecute及CanExecute事件的處理程式。 它還提供了將 CommandBinding 和 InputBinding 對象註冊到特定類的方法。除此之外,CommandManager還通過RequerySuggested事件提供了一種方法,用於通知Command觸發其CanExecuteChanged事件。

而CommandManager提供的InvalidateRequerySuggested方法可以強制 CommandManager 引發 RequerySuggested事件,換句話說,調用InvalidateRequerySuggested方法就可以通知指定的Command觸發其CanExecuteChanged事件。

那CanExecuteChanged事件又做了什麼呢?不妨看看RoutedCommand中的實現:

 1 public class RoutedCommand : ICommand
 2 {
 3     ...
 4 
 5     public event EventHandler CanExecuteChanged
 6     {
 7         add
 8         {
 9             CommandManager.RequerySuggested += value;
10         }
11         remove
12         {
13             CommandManager.RequerySuggested -= value;
14         }
15     }
16     
17     ...
18 }

可以看到,RoutedCommand中的CanExecuteChanged事件不過是對CommandManager中RequerySuggested事件的封裝,即註冊到CanExecuteChanged的事件處理程式實際是註冊到了CommandManager的RequerySuggested事件上。所以當通過調用CommandManager的InvalidateRequerySuggested方法來引發RequerySuggested事件時就會調用Command的CanExecuteChanged事件處理程式。然而,在使用RoutedCommand時,不用為其CanExecuteChanged指定事件處理程式,只消為關聯的CommandBinding中的CanExecute指定事件處理器,命令源的狀態就會按照CanExecute的事件處理器中的邏輯進行更新。是不是有點奇怪?既然是按照CanExecute中的邏輯來更新命令源的狀態,那說明CanExecute事件被引發了,是被誰引發的呢?彆著急,馬上揭曉答案!

既然Command能夠影響命令源的狀態,那就追本溯源,來看看命令源中和它的Command屬性,以Button為例,可以看到Button的CommandProperty依賴屬性在初始化時被指定了一個名為OnCommandChanged的回調函數,

1 CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ButtonBase), new FrameworkPropertyMetadata(null, OnCommandChanged));

其實現如下:

 1 private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 2 {
 3     ButtonBase buttonBase = (ButtonBase)d;
 4     buttonBase.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
 5 }
 6 
 7 private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
 8 {
 9 10     if (newCommand != null)
11     {
12         HookCommand(newCommand);
13     }
14 }
15 
16 private void HookCommand(ICommand command)
17 {
18     CanExecuteChangedEventManager.AddHandler(command, OnCanExecuteChanged);
19     UpdateCanExecute();
20 }
21 
22 private void OnCanExecuteChanged(object sender, EventArgs e)
23 {
24     UpdateCanExecute();
25 }
26 
27 private void UpdateCanExecute()
28 {
29     if (Command != null)
30     {
31         CanExecute = CommandHelpers.CanExecuteCommandSource(this);
32     }
33     else
34     {
35         CanExecute = true;
36     }
37 }

最後一個方法UpdateCanExecute中的CanExecute屬性與Button的IsEnabled屬性關聯,CommandHelpers.CanExecuteCommandSource方法實現如下:

 1 internal static bool CanExecuteCommandSource(ICommandSource commandSource)
 2 {
 3     ICommand command = commandSource.Command;
 4     if (command != null)
 5     {
 6         object commandParameter = commandSource.CommandParameter;
 7         IInputElement inputElement = commandSource.CommandTarget;
 8         RoutedCommand routedCommand = command as RoutedCommand;
 9         if (routedCommand != null)
10         {
11             if (inputElement == null)
12             {
13                 inputElement = (commandSource as IInputElement);
14             }
15 
16             return routedCommand.CanExecute(commandParameter, inputElement);
17         }
18 
19         return command.CanExecute(commandParameter);
20     }
21 
22     return false;
23 }

嗯,是不是有點內味了?

當Button的Command屬性值發生改變時,HookCommand方法通過調用UpdateCanExecute方法調用了CommandHelpers的CanExecuteCommandSource方法,而後者會調用RoutedCommand中的CanExecute方法(如果Command非RoutedCommand類型,則直接調用ICommand介面的CanExecute方法),此CanExecute方法會引發CommandManager.CanExecuteEvent路由事件,最終事件被與此命令關聯的CommandBinding中的CanExecute事件處理器捕獲並處理,CanExecute的事件處理器中有刷新命令源狀態的邏輯。

但是,請註意,這隻是在Button的Command屬性值發生變化時刷新Button的狀態,那Command屬性沒有改變時,是如何刷新狀態的呢?玄機還在HookCommand中,它會將UpdateCanExecute方法與CanExecuteChangedEventManager類進行關聯(這裡就不展開講述CanExecuteChangedEventManager類了,感興趣的話可以自行研究),具體關聯方法就是將上述的OnCanExecuteChanged方法作為事件處理器綁定到Command的CanExecuteChanged事件,還記得嗎,這個CanExecuteChanged事件就是RoutedCommand中對CommandManager.RequerySuggested事件進行封裝的那個,想起來了吧?

在這裡對命令源狀態更新方法稍微做個總結:

首先是通過對CommandManager的RequerySuggested事件的封裝將RoutedCommand的CanExecuteChanged與CommandManager的InvalidateRequerySuggested方法關聯,這樣當調用InvalidateRequerySuggested方法時會引發RoutedCommand的CanExecuteChanged事件;

其次,通過ICommandSource的Command屬性將其CanExecuteChanged事件與關聯的CommandBinding的CanExecute事件(或與自定義命令的CanExecute方法)關聯;

這樣一來,當調用CommandManager的InvalidateRequerySuggested方法時,最終會調用指定命令的關聯CommandBinding中的CanExecute事件(或指定自定義命令的CanExecute方法)。

到這裡,更新命令源的路徑基本通了,但是,萬事俱備只欠東風!是誰在什麼時候調用了CommandManager的InvalidateRequerySuggested方法呢?

通過ILSpy來簡單看一下:

 

 居然有這麼多調用者。那就來看看第一處調用吧:

 1 // System.Windows.Input.CommandDevice
 2 private void PostProcessInput(object sender, ProcessInputEventArgs e)
 3 {
 4     if (e.StagingItem.Input.RoutedEvent == InputManager.InputReportEvent)
 5     {
 6         ...
 7     }
 8     else if (e.StagingItem.Input.RoutedEvent == Keyboard.KeyUpEvent || e.StagingItem.Input.RoutedEvent == Mouse.MouseUpEvent || e.StagingItem.Input.RoutedEvent == Keyboard.GotKeyboardFocusEvent || e.StagingItem.Input.RoutedEvent == Keyboard.LostKeyboardFocusEvent)
 9     {
10         CommandManager.InvalidateRequerySuggested();
11     }
12 }

可以看到,當鍵盤、滑鼠事件發生時,都會引發命令源狀態更新。

到此,關於CommandManager及如何更新命令源狀態的講述告一段落,接下來說說如何自定義命令。

5.自定義命令

看到此處,應該對WPF自帶的RoutedCommand有了比較深入的理解,RoutedCommand本質是路由事件,它的執行不可避免的要經歷發起路由事件、構建路由路徑、沿路由路徑執行命令處理程式等這一複雜的流程,所以當我們在使用命令時,可能就會面臨兩種選擇:

1、我的命令需要進行路由;

2、我的命令不需要路由,我想讓它輕裝上陣;

針對上述兩種情況,自然就有了兩種自定義命令的方式:

方式一:命令需要路由,那麼可以直接實例化RoutedCommand或RoutedUICommand,或擴展這兩個實現。

下麵主要就方式二進行展開。

ICommand介面包含一個事件CanExecuteChanged和兩個方法CanExecute和Execute,

1 public interface ICommand
2 {
3     event EventHandler CanExecuteChanged;
4     bool CanExecute(object parameter);
5     void Execute(object parameter);
6 }

前文有提及,執行命令時,當命令不是RoutedCommand類型時,WPF會先調用ICommand介面的CanExecute方法,判斷此時命令是否可以執行,然後再調用其Execute方法,命令的執行就完成了。所以在實現ICommand介面時,只要派生類的Execute方法能處理命令的操作即可。

直接上代碼:

 1 class MyCommand : ICommand
 2 {
 3     private readonly Predicate<object> _canExecuteMethod;
 4     private readonly Action<object> _executeMethod;
 5 
 6     public event EventHandler CanExecuteChanged
 7     {
 8         add
 9         {
10             CommandManager.RequerySuggested += value;
11         }
12         remove
13         {
14             CommandManager.RequerySuggested -= value;
15         }
16     }
17 
18     public MyCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod = null)
19     {
20         _executeMethod = executeMethod ?? throw new ArgumentNullException(nameof(executeMethod));
21         _canExecuteMethod = canExecuteMethod;
22     }
23 
24     public bool CanExecute(object parameter)
25     {
26         if (_canExecuteMethod != null)
27         {
28             return _canExecuteMethod(parameter);
29         }
30         return true;
31     }
32 
33     public void Execute(object parameter)
34     {
35         _executeMethod(parameter);
36     }
37 }

總結

  1. 命令系統包含ICommand,ICommandSource,命令目標及CommandBinding 四個基本要素,但是ICommandSource中的CommandTarget屬性只在命令是RoutedCommand時才有用,否則在命令執行時會被直接忽略;
  2. RoutedCommand顧名思義,其本質還是路由事件,但它只負責發起路由事件,並不執行命令邏輯,命令邏輯是由與具體命令關聯的CommandBinding來執行的;
  3. 由於RoutedCommand是基於路由事件的,因此其發起路由事件、構建路由路徑、沿路由路徑執行命令處理程式等這一複雜的流程勢必會對執行效率產生不好的影響,所以如果不需要命令進行路由,可以構建簡單的自定義命令。
  4. 自定義命令時,如果希望通過命令系統來改變命令源的可執行狀態,需要在實現時通過CanExecuteChanged事件對CommandManager的RequerySuggested事件進行封裝。

 

最後來一張命令系統的UML圖:

參考

1.命令概述 - WPF .NET Framework | Microsoft Learn

本文來自博客園,作者:葉落勁秋,轉載請註明原文鏈接:https://www.cnblogs.com/tianlang358/p/17077102.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概要 前端時間做尺規作圖相關的動畫的時候,封裝了一個圓規的動畫,順便研究了下 manim 庫的動畫函數。 manim 本身就是做動畫的庫,所以,基於它封裝自定義的動畫非常方便。 動畫原理 對於單個的元素,manim本身就提供了非常多的動畫函數。 比如:創建/消除的動畫,移動元素的動畫,旋轉元素的動畫 ...
  • 本文記錄一次線上 GC 問題的排查過程與思路,希望對各位讀者有所幫助。過程中也走了一些彎路,現在有時間沉澱下來思考並總結出來分享給大家,希望對大家今後排查線上 GC 問題有幫助。 ...
  • 本文已收錄至Github,推薦閱讀 👉 Java隨想錄 微信公眾號:Java隨想錄 CSDN: 碼農BookSea 人的一切痛苦,本質上都是對自己的無能的憤怒。——王小波 ZGC有人稱它為Zero GC,其實“Z”並非什麼專業名詞的縮寫,這款收集器的名字就叫作Z Garbage Collector ...
  • 摘要:AQS 的全稱為(AbstractQueuedSynchronizer),AQS 是一個用來構建鎖和同步器的框架,使用 AQS 能簡單且高效地構造出應用廣泛的大量的同步器。 本文分享自華為雲社區《【高併發】AQS中的CountDownLatch、Semaphore與CyclicBarrier核 ...
  • 題目背景(題目鏈接) 題目描述 給定一個N*M方格的迷宮,迷宮裡有T處障礙,障礙處不可通過。 在迷宮中移動有上下左右四種方式,每次只能移動一個方格。數據保證起點上沒有障礙。 給定起點坐標和終點坐標,每個方格最多經過一次,問有多少種從起點坐標到終點坐標的方案。 輸入格式 第一行為三個正整數 N,M,T ...
  • 當我們在製作PDF文件或者PPT演示文稿的時候,為了讓自己的文件更全面詳細,就會在文件中添加附件。並且將相關文檔附加到 PDF 可以方便文檔的集中管理和傳輸。那麼如何添加或刪除 PDF 中的附件呢?別擔心,我們可以通過編程方式輕鬆實現此操作。下麵是我整理的具體步驟,並附上Java代碼供大家參考。 文 ...
  • Java實現BP神經網路,內含BP神經網路類,採用MNIST數據集,包含伺服器和客戶端程式,可在伺服器訓練後使客戶端直接使用訓練結果,界面有畫板,可以手寫數字 ...
  • 一 引入 考慮實現一種三軸機器人控制項。 三軸機器人用來將某種工件從一個位置運送到另一個位置。 其X軸為手臂軸,可以正向和反向運動,它處於末端,直接接觸工件; 其T軸為旋轉軸,可以對手臂進行旋轉; 其Z軸為升降軸,可以對手臂和旋轉部分進行升降。 二 RobotControl 定義出機器人的軸動作枚舉, ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...