深入淺出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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...