2020年4月14日 星期二

WPF Prism 2 - 啟動 WPF 程式之後,在不使用 Module 情況下,如何自動顯示 Region 內的 View

WPF Prism 2 - 啟動 WPF 程式之後,在不使用 Module 情況下,如何自動顯示 Region 內的 View

在上一篇文章中,已經說明如何使用 Prism Template Pack 這個 Visual Studio 擴充功能工具,建立一個使用 Prism 開發框架的 WPF 專案,並且在這個專案中展示出如何自動加入、切換、移除 Region 內的 View,這一切的呈現的功能都是在執行時期所發生的。
不過,關於上一篇文章的範例專案若一開始執行的話,將會發現到新顯示的視窗內,並沒有任何 View 顯示出來,必須要點選按鈕之後,才會顯示出來;因此,若想要做到 App 一起動的話,就要自動顯示在螢幕上的功能,可以參考這篇文章的做法,這裡是使用了 [IRegionManager.RegisterViewWithRegion] 方法來做到,不過,在這裡還是同樣的,不使用 Code Behind 的作法,而是採用了 MVVM 的做法,那就是在 Shell 的 ViewModel 內來完成這個工作。
這個說明專案的原始碼位於 WPFPrismNoModuleAutoView

準備工作

  • 首先,先要安裝 [Prism Template Pack] 到 Visual Studio 2019 內
  • 打開 Prism Template Pack 擴充功能網站
  • 下載並且安裝這個擴充功能

建立 WPF for Prism 的專案

  • 打開 Visual Studio 2019
  • 點選右下方的 [建立新的專案] 按鈕
  • [建立新專案] 對話窗將會顯示在螢幕上
  • 從[建立新專案] 對話窗的中間區域,找到 [Prism Blank App (WPF)] 這個專案樣板選項,並且選擇這個項目
    若沒有看到這個選項,則表示你的 Visual Studio 2019 開發環境中,還沒有安裝 Prism Template Pack 擴充功能
  • 點選右下角的 [下一步] 按鈕
  • 現在 [設定新的專案] 對話窗將會出現
  • 請在這個對話窗內,輸入適當的 [專案名稱] 、 [位置] 、 [解決方案名稱]
    在這裡請輸入 [專案名稱] 為 WPFPrismNoModuleAutoView
  • 在最下方的 [架構] 部分,建議選取最新的 [.NET Framework 4.8]
  • 完成後,請點選 [建立] 按鈕
  • 當出現 [PRISM PROJECT WIZARD] 對話窗的時候
  • 請在 [Select Container] 選擇容器這個欄位之下拉選單,選擇你要使用的 DI 相依性注入容器,我個人習慣使用 Unity 這個 Ioc 容器
  • 之後,點選 [CREATE PROJECT] 這個按鈕
稍微等會一段時間,具有 Prism 開發框架的 WPF 專案將會建立起來

加入 View

  • 首先,滑鼠右擊 [Views] 資料夾
  • 選擇 [加入] > [新增項目]
  • 此時,[新增項目] 對話窗將顯示出來
  • 請在該對話窗的左方,展開節點到 [已安裝] > [Visual C#] > [Prism] > [WPF]
  • 在中間區域選擇 [Prism UserControl (WPF)] 選項
  • 在下方名稱欄位輸入 MyView
  • 最後點選 [新增] 按鈕
此時,將會看到該專案的 [Views] 資料夾內新產生了一個 [MyView.xaml] 這個檔案,另外,在 [ViewModel] 資料夾內也產生了一個 [MyViewViewModel] 這個類別檔案。
打開 [MyView.xaml] 檔案,填入底下 XAML 標記宣告
<UserControl x:Class="WPFPrismHelloWorld.Views.MyView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid Background="LightGreen">
        <TextBlock Text="Hello World" FontSize="48" Foreground="HotPink"/>
    </Grid>
</UserControl>

設定 Region 要自動顯示的 View

  • 在 [ViewModels] 資料夾下,找到並且打開 [MainWindowViewModel.cs] 這個檔案
  • 找到建構式,加入 IRegionManager 這個參數型別,要求容器使用建構式注入的方式,注入這個具體實作類別進來
  • 在建構函式內,使用 [IRegionManager.RegisterViewWithRegion] 這個方法,進行 Region 要使用哪個 View 的註冊與加入的行為要求
在 RegisterViewWithRegion 方法的第一個參數,使用的 "ContentRegion" 這個字串,這是因為在這個 App Shell 中,僅宣告一個 Region,而該 Region 的名稱就是 "ContentRegion";這點可以從 [MainWindow.xaml] 這個檔案內看的出來
<Grid>
    <ContentControl prism:RegionManager.RegionName="ContentRegion" />
</Grid>
關於 [MainWindowViewModel.cs] 的完整程式碼,將會如下所示
using Prism.Mvvm;
using Prism.Regions;
using WPFPrismNoModuleAutoView.Views;

namespace WPFPrismNoModuleAutoView.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";
        private readonly IRegionManager regionManager;

        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        public MainWindowViewModel(IRegionManager regionManager)
        {
            this.regionManager = regionManager;
            regionManager.RegisterViewWithRegion("ContentRegion", typeof(MyView));
        }
    }
}

執行與測試

這個範例專案的執行結果將會如底下螢幕截圖




2020年4月13日 星期一

WPF Prism 1 - 首次使用 Prism 開發框架來製作一個 Hello World 教學說明程式

WPF Prism 1 - 首次使用 Prism 開發框架來製作一個 Hello World 教學說明程式

在這篇文章,將會帶領想使用 Prism Library 這套開發框架,使用 MVVM 與許多 Prims 內建的設計模式 Design Pattern 功能,來讓想要了解如何進行 WPF 專案開發的程式設計師有實際練習實作 Hands-on 的參考說明教學。
在這個練習中,將會設計一個 Shell ,其中只有一個 Region ,不過,卻要提供三個按鈕,其中兩個按鈕,可以分別動態的加入所指定 View 到這個 Region 內,如此,將會看到這個視窗的畫面將會有所變化。而另外一個按鈕,將會把 Region 的 View 移除。
在這個練習中,將不會使用到 Module 這項功能,所有的相關設計,都將會在同一個專案 (.NET 組件 Assembly ) 下來開發。
這裡將會是一開始執行的畫面,在該畫面中,實際上是在 MainWindow 這個 Window 元件下所設計的成果,而最上方將會有三個按鈕,而在下方,則是一個 Region 區域,這塊區域將會使用 Prism 提供的機制,可以在執行時期,動態的新增、移除、導航到其他 View 上。
當點選了 Hi 按鈕之後,此時,將會透過 MainWindow 之 ViewModel 類別內(注意,這裡沒有使用到 callbehind 的設計方式,而 ViewModel 的指派,也是透過 Prism 提供 ViewModel Locator 定位器來自動設定完成) 的 DelegateCommand 命令物件進行執行相關委派程式碼,在此,將會透過 RegionManager 這個物件(該物件使用相依性注入的方式,透過建構式來取得該物件,也就是使用了建構式注入方法)來進行動態的指定使用 [MyView] 這個 檢視 View 到這個應用程式視窗的下方,如此,將會變成底下的畫面截圖。
第二個按鈕, Thank You ,設計方式如同 Hi 按鈕,只不過在這裡將會 [YourView] 注入到這個視窗的 Region 內。
第三個按鈕,則是更加簡單,那就是把這個 Region 內的 View ,全部都移除,當然,所完成的執行結果就是該視窗下方的 Region 區域,將沒有任何動態內容顯示出來。

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。

這個說明專案的原始碼位於 WPFPrismHelloWorld

準備工作

  • 首先,先要安裝 [Prism Template Pack] 到 Visual Studio 2019 內
  • 打開 Prism Template Pack 擴充功能網站
  • 下載並且安裝這個擴充功能

建立 WPF for Prism 的專案

  • 打開 Visual Studio 2019
  • 點選右下方的 [建立新的專案] 按鈕
  • [建立新專案] 對話窗將會顯示在螢幕上
  • 從[建立新專案] 對話窗的中間區域,找到 [Prism Blank App (WPF)] 這個專案樣板選項,並且選擇這個項目
    若沒有看到這個選項,則表示你的 Visual Studio 2019 開發環境中,還沒有安裝 Prism Template Pack 擴充功能
  • 點選右下角的 [下一步] 按鈕
  • 現在 [設定新的專案] 對話窗將會出現
  • 請在這個對話窗內,輸入適當的 [專案名稱] 、 [位置] 、 [解決方案名稱]
    在這裡請輸入 [專案名稱] 為 WPFPrismHelloWorld
  • 在最下方的 [架構] 部分,建議選取最新的 [.NET Framework 4.8]
  • 完成後,請點選 [建立] 按鈕
  • 當出現 [PRISM PROJECT WIZARD] 對話窗的時候
  • 請在 [Select Container] 選擇容器這個欄位之下拉選單,選擇你要使用的 DI 相依性注入容器,我個人習慣使用 Unity 這個 Ioc 容器
  • 之後,點選 [CREATE PROJECT] 這個按鈕
稍微等會一段時間,具有 Prism 開發框架的 WPF 專案將會建立起來
WPF for Prism

加入兩個 View 與進行容器註冊

  • 首先,滑鼠右擊 [Views] 資料夾
  • 選擇 [加入] > [新增項目]
  • 此時,[新增項目] 對話窗將顯示出來
  • 請在該對話窗的左方,展開節點到 [已安裝] > [Visual C#] > [Prism] > [WPF]
  • 在中間區域選擇 [Prism UserControl (WPF)] 選項
  • 在下方名稱欄位輸入 MyView
  • 最後點選 [新增] 按鈕
此時,將會看到該專案的 [Views] 資料夾內新產生了一個 [MyView.xaml] 這個檔案,另外,在 [ViewModel] 資料夾內也產生了一個 [MyViewViewModel] 這個類別檔案。
打開 [MyView.xaml] 檔案,填入底下 XAML 標記宣告
<UserControl x:Class="WPFPrismHelloWorld.Views.MyView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid Background="LightGreen">
        <TextBlock Text="Hello World" FontSize="48" Foreground="HotPink"/>
    </Grid>
</UserControl>
接下來,繼續產生一個 YourView 檢視
  • 首先,滑鼠右擊 [Views] 資料夾
  • 選擇 [加入] > [新增項目]
  • 此時,[新增項目] 對話窗將顯示出來
  • 請在該對話窗的左方,展開節點到 [已安裝] > [Visual C#] > [Prism] > [WPF]
  • 在中間區域選擇 [Prism UserControl (WPF)] 選項
  • 在下方名稱欄位輸入 YourView
  • 最後點選 [新增] 按鈕
此時,將會看到該專案的 [Views] 資料夾內新產生了一個 [YourView.xaml] 這個檔案,另外,在 [ViewModel] 資料夾內也產生了一個 [YourViewViewModel] 這個類別檔案。
打開 [YourView.xaml] 檔案,填入底下 XAML 標記宣告
<UserControl x:Class="WPFPrismHelloWorld.Views.YourView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid Background="Yellow">
        <TextBlock Text="Thank You" FontSize="48" Foreground="LightBlue"/>
    </Grid>
</UserControl>
  • 在該專案根目錄下,找到 App.xaml 這個檔案節點
  • 展開該節點,將會看到一個 [App.xaml.cs] 這個節點
  • 滑鼠雙擊打開 [App.xaml.cs] 這個節點
  • 在 [App.xaml.cs] 檔案內,找到 RegisterTypes 方法
  • 將該方法修改成為底下的程式碼
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<MyView>(nameof(MyView));
    containerRegistry.RegisterForNavigation<YourView>(nameof(YourView));
}

設計 Shell 頁面架構

  • 在 [Views] 資料夾內,找到並打開 [MainWindow.xaml] 檔案
  • 將底下的 XAML 標記宣告,替換掉這個檔案原先內容
<Window x:Class="WPFPrismHelloWorld.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525">
    <Grid Background="LightGray">
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel
            HorizontalAlignment="Center"
            Orientation="Horizontal">
            <Button Command="{Binding SayHiCommand}" Width="150">Hi</Button>
            <Button Command="{Binding ThankYouCommand}" Width="150">Thank You</Button>
            <Button Command="{Binding RemoveCommand}" Width="150">Remove</Button>
        </StackPanel>
        <ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
    </Grid>
</Window>
  • 在 [ViewModels] 資料夾內,找到並打開 [MainWindowViewModel.cs] 檔案
  • 使用底下 C# 程式碼替換掉原先內容
using Prism.Commands;
using Prism.Ioc;
using Prism.Mvvm;
using Prism.Regions;
using WPFPrismHelloWorld.Views;

namespace WPFPrismHelloWorld.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";
        private readonly IRegionManager regionManager;

        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
        public DelegateCommand SayHiCommand { get; set; }
        public DelegateCommand RemoveCommand { get; set; }
        public DelegateCommand ThankYouCommand { get; set; }
        public MainWindowViewModel(IRegionManager regionManager, IContainerExtension container)
        {
            SayHiCommand = new DelegateCommand(() =>
            {
                IRegion region = regionManager.Regions["ContentRegion"];
                region.RemoveAll();
                var view = container.Resolve<MyView>();
                region.Add(view);
            });
            ThankYouCommand = new DelegateCommand(() =>
            {
                IRegion region = regionManager.Regions["ContentRegion"];
                region.RemoveAll();
                var view = container.Resolve<YourView>();
                region.Add(view);
            });
            RemoveCommand = new DelegateCommand(() =>
            {
                IRegion region = regionManager.Regions["ContentRegion"];
                region.RemoveAll();
            });
        }
    }
}

執行與測試

現在可以執行這個使用 Prism 開發的 WPF 專案,看看是不是如同最初說明一樣的方式來進行運作。



2020年4月10日 星期五

Blazor 綁定屬性變更 Part 3 - 透過 PropertyChanged.Fody 套件,使用 INotifyPropertyChanged 機制,簡化程式碼設計

Blazor 綁定屬性變更 Part 3 - 透過 PropertyChanged.Fody 套件,使用 INotifyPropertyChanged 機制,簡化程式碼設計

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。

Blazor 綁定屬性變更 Part 3 - 透過 PropertyChanged.Fody 套件,使用 INotifyPropertyChanged 機制,簡化程式碼設計
這是本系列文章的第三個部分,在前一篇文章使用 INotifyPropertyChanged 提供介面,針對要使用的資料模型,都需要進行實作出 INotifyPropertyChanged 介面,可是,這樣會造成該類別內的屬性,都需要使用 含有支援欄位的屬性 的設計方式,而且在設定存取子 Set Accessor 的部分,也需要增加額外的程式碼來進行拋出 PropertyChanged 的事件;對於這樣的設計方式會顯得有些繁雜而且容易出錯。
因此,在這篇文章中,將會使用另一種做法,那就是使用 PropertyChanged.Fody 這個套件,將許多繁雜的程式碼,透過這個套件來交由編譯器來產生出來;另外,該套件也提供了許多額外好用的機制,On_PropertyName_Changed 就是一個,只要依照該套件規範的方式來設計,當指定的屬性值有變更的時候,就會自動觸發與執行這個方法。
這個說明專案的原始碼位於 bzINPCbyFody

建立 Blazor Server-Side 的專案

首先,還是先來建立一個新的專案
  • 打開 Visual Studio 2019
  • 點選右下方的 [建立新的專案] 按鈕
  • [建立新專案] 對話窗將會顯示在螢幕上
  • 從[建立新專案] 對話窗的中間區域,找到 [Blazor 應用程式] 這個專案樣板選項,並且選擇這個項目
  • 點選右下角的 [下一步] 按鈕
  • 現在 [設定新的專案] 對話窗將會出現
  • 請在這個對話窗內,輸入適當的 [專案名稱] 、 [位置] 、 [解決方案名稱]
    在這裡請輸入 [專案名稱] 為 bzINPCbyFody
  • 完成後,請點選 [建立] 按鈕
  • 當出現 [建立新的 Blazor 應用程式] 對話窗的時候
  • 請選擇最新版本的 .NET Core 與 [Blazor 伺服器應用程式]
  • 完成後,請點選 [建立] 按鈕
稍微等會一段時間,Blazor 專案將會建立起來

安裝 PropertyChanged.Fody NuGet 套件

  • 滑鼠右擊 [相依性] 節點
  • 選擇 [管理 NuGet 套件]
  • 在 [管理 NuGet 套件] 視窗內,點選 [瀏覽] 標籤頁次
  • 搜尋 PropertyChanged.Fody 這個套件
  • 找到這個套件,點選 [安裝] 按鈕,將這個套件安裝起來

建立需求網頁 Balzor 元件

  • 打開 [Pages] 資料夾內的 [Index.razor] 檔案
  • 使用底下 Razor 元件標記與程式碼,替換該檔案內的原有內容
@page "/"

<h1>在 Blazor 透過 PropertyChanged.Fody 套件,使用 INotifyPropertyChanged 事件,設計屬性變更的需求</h1>
<div class="form-group">
    <label for="FirstName">名</label>
    <input id="FirstName" class="form-control" @bind="MyPerson.FirstName" />
</div>
<div class="form-group">
    <label for="LastName">姓</label>
    <input id="LastName" class="form-control" @bind="MyPerson.LastName" />
</div>
<div class="form-group">
    <label for="Age">年紀</label>
    <input type="number" id="Age" class="form-control"
           @bind="MyPerson.Age" @bind:event="oninput" />
</div>

<button type="submit" class="btn btn-primary" disabled="@MyPerson.IsDisabled">Submit</button>

<div class="text-danger">
    @MyPerson.Age
</div>
<div class="text-danger">
    @MyPerson.AgeRange
</div>

@code{
    class Person : System.ComponentModel.INotifyPropertyChanged
    {
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public string AgeRange { get; set; }
        public bool IsDisabled { get; set; } = true;

        public void OnAgeChanged()
        {
            if (Age < 13)
            {
                AgeRange = "你是小朋友";
                IsDisabled = true;
            }
            else if (Age < 20)
            {
                AgeRange = "你是年青人";
                IsDisabled = false;
            }
            else if (Age < 28)
            {
                AgeRange = "你是青年人";
                IsDisabled = false;
            }
            else
            {
                AgeRange = "你是成年人";
                IsDisabled = false;
            }
        }
    }

    Person MyPerson = new Person();
}
在這個 index.razor 檔案內,對於 HTML 標記的設計上,原則上沒有使用到任何宣告,如同上一篇文章所提到的內容。
不過,在這裡將會針對 Person 類別,同樣也實作了 INotifyPropertyChanged 這個介面,不過,所有的屬性,將會使用 自動實作屬性 來完成,不過,這裡將會產生疑問,那就是當屬性值有異動的時候,要如何觸發 PropertyChanged 這個事件呢?
還記得剛剛之前有安裝了 PropertyChanged.Fody 這個套件,只要建置專案,對於剛剛所需要處理的程式碼,這個套件將會幫忙產生其他會用到的程式碼。
使用這樣的設計方式,有點像是在設計 ViewModel,將會把許多與頁面有關的程式碼,都寫到這個類別內,不過,透過了資料綁定機制,形成鬆散耦合的關係;另外,這裡也會使用 PropertyChanged.Fody 套件提供的功能,可以撰寫一個屬性變更的 callback 函式,也就是說,當屬性值有變動的時候,會自動執行的方法。
想要使用這樣的功能,僅需要使用特定的方法簽章來命名即可, PropertyChanged.Fody 套件會自動完成其他相關程式碼運作機制。例如,想要針對當 Age 屬性有異動的時候,可以自動執行某個方法,這裡需要使用這樣的方法簽章來命名
public void On_PropertyName_Changed(){...}
其中,對於方法名稱的最前面需要有 On 這個字串,緊接著是這個屬性的名稱,最後使用 Changed 最為結尾,一旦設計出這個方法之後,只要 Age 的屬性有異動的時候,將會自動執行 OnAgeChanged 這個方法。
現在,這個專案設計工作已經完成了,現在可以來比對這三種的不同作法,將會發現到這裏所提出的作法,所設計出來的程式碼最為簡單與清爽,而且還增加了許多不錯的功能可以來應用。
底下將會是執行結果