漫談WPF資料繫結-使用Expression Blend 4

by Vivid 18. 五月 2011 01:43

資料繫結(Data Binding)可以將兩個物件關聯在一起,其中一個物件的值變更時,能夠更新到另一個物件。例如將TextBox控制項的Text屬性關聯到Label控制項的Content屬性,TextBox控制項的Text屬性值變更時,會自動更新到Label控制項Content屬性。本文將介紹WPF資料繫結的基本概念,並介紹如何使用Microsoft Expression Blend來設定繫結。

在WPF應用程式之中,您可以直接在XAML標籤建立資料繫結,WPF會透過標記擴充程式(Markup extension)自動處理繫結;或者是利用程式建立Binding物件。在資料來源方面,你可以將依存屬性(Dependency properties)繫結到XML資料與.NET CLR物件,包含ADO.NET物件、其它Managed物件,或dynamic物件。

要讓兩個物件的資料繫結動作能夠同步,則必需使用屬性。讓資料來源的來源屬性(Source Property)關聯到目標屬性(Target Property),以上述的例子來說,TextBox控制項的Text屬性便是來源屬性;Label控制項的Content屬性便是目標屬性(Target Property)。

特別要注意一點,目標屬性(Target Property)。必需是一個依存屬性(Dependency Property),以便讓WPF能夠監控屬性值是否有變動,才能進行資料繫結的同步動作。在WPF中預設除唯讀屬性之外,絕大多數的UIElement類別的屬性都是依存屬性,也支援資料繫結的能力。

讓我們利用Microsoft Expression Blend 4來說明如何將TextBox控制項的Text屬性關聯到Label控制項的Content屬性之操作步驟,假設目前WPF視窗中包含一個TextBox與Label控制項如下:

<TextBox x:Name = "textBox" HorizontalAlignment = "Left" Height = "40" Margin = "64,32,0,0" TextWrapping = "Wrap" Text = "TextBox" VerticalAlignment = "Top" Width = "152" />

<Label HorizontalAlignment = "Left" Height = "56" Margin = "64,88,0,0" VerticalAlignment = "Top" Width = "152" />

點選畫面上的Label控制項,從屬性視窗選取Content屬性後方的「Advanced options」->「Data Binding」,請參考圖1所示。

clip_image002

圖 1:設定資料繫結

在「Create Data Binding」視窗中,選取「Element Property」,屬性設定為Text。代表將Label控制項的Content屬性繫結到TextBox控制項的Text屬性,請參考圖2所示。

clip_image004

圖 2:Create Data Binding

按下「OK」按鈕後,Microsoft Expression Blend 4就會自動在Label控制項的Content屬性產生以下繫結的語法:

<TextBox x:Name = "textBox" HorizontalAlignment = "Left" Height = "40" Margin = "64,32,0,0" TextWrapping = "Wrap" Text = "TextBox" VerticalAlignment = "Top" Width = "152"/>

<Label Content = "{Binding Text, ElementName=textBox}" HorizontalAlignment = "Left" Height = "56" Margin = "64,88,0,0" VerticalAlignment = "Top" Width = "152" />

繫結語法使用一個大括號 { },其中Binding是標記擴充程式(Markup Extension)類別的名稱,ElementName指明來源的項目,Text則是目標的屬性。ElementName與Text屬性之間需要使用「,」號區隔。

若執行程式進行測式,當TextBox控制項的內容變動時,Label控制項會馬上顯示出最新的內容。

傷腦筋的是,若使用Visual Studio 2010來設定繫結,則產生出來的繫結語法會有點不同。例如在Visual Studio 2010之中選取畫面上的Label控制項,從屬性視窗選取Content屬性後方的「Advanced options」->「Apply Data Binding」。設定Source的ElementName為textBox;Path為Text,請參考圖3、4所示。Path必須指向一個Public屬性。

clip_image006

圖 3:在Visual Studio 2010中設定繫結來源

clip_image008

圖 4:在Visual Studio 2010中設定繫結Path

則Visual Studio 2010產生出來的繫結語法為:

<Label Content = "{Binding ElementName=textBox, Path=Text}" HorizontalAlignment = "Left" Height = "56" Margin = "64,88,0,0" VerticalAlignment = "Top" Width = "152" />

使用程式繫結

當然資料繫結也可以直接透過程式碼來完成。Expression Blend 4也內建程式撰寫視窗,並支援智慧型感知功能以及IntelliSense支援。您可以從「Projects」視窗,雙擊Xaml檔案相關聯的cs檔來開啟程式視窗。

舉例來說,若目前視窗中有一個TextBox控制項以及一個Label控制項,分別設定其name為txt與lbl。

目前XAML定義如下:

<TextBox HorizontalAlignment = "Left" Height = "40" Margin = "80,32,0,0" TextWrapping = "Wrap" Text = "TextBox" VerticalAlignment = "Top" Width = "152" Name = "txt" />

<Label Content = "Label" HorizontalAlignment = "Left" Height = "40" Margin = "80,96,0,0" VerticalAlignment = "Top" Width = "152" Name = "lbl" />

在Window相關聯的cs檔案中建構函式InitializeComponent這行程式後加入程式碼:

public partial class Window1: Window {

public Window1 () {

this.InitializeComponent();

Binding b = new Binding ( );

b.Source = txt;

b.Path = new PropertyPath ( "Text" );

lbl.SetBinding ( Label.ContentProperty , b );

}

}

建立一個Binding物件,設定它的Source屬性為TextBox控制項;Path屬性為一個PropertyPath物件,並傳入Text字串初始化PropertyPath物件。最後在目標物件上呼叫SetBinding方法,WPF會建立Binding Expression物件將兩者關聯一起。TextBox、Label控制項與Binding物件的關係請參考圖5。

clip_image010

圖 5:TextBox、Label控制項與Binding物件的關係

繫結方向性

WPF Binding物件的資料繫結支援多種方向性的設定,您可以利用Mode屬性明確地透過程式碼或XAML來變更其行為,可以設定的值包含:

  • OneWay。當Source屬性變更時會更新Target屬性,但Target屬性變更時不更新source屬性。
  • TwoWay. 當Source屬性變更時會更新Target屬性,但Target屬性變更時也會更新source屬性。適合用在資料編輯。
  • OneWayToSource. 和OneWay相反,Target屬性變更時更新source屬性,但source變更時,不修改Target屬性。
  • OneTime. source property會初始化target property,但後續變動source或target屬性都不同步。
  • Default:使用目標的預設Binding。WPF中每一個UI項目都有一個預設的Binding Mode,可能是OneWay或TwoWay,例如TextBox控制項預設是TwoWay;而Label控制項預設是OneWay。

在Expression Blend 之中要撰寫一般的事件處理常式很不方便,因其原用意是強調在UI、動畫等設計而不是用來撰寫程式碼。若需要撰寫程式碼,可以切換到Visual Studio 2010,從Expression Blend的Projects視窗,選取專案,然後按下「Edit in Visual Studio」選項,就會開啟Visual Studio 2010。在Expression Blend中您可以自行透過程式註冊事件,還好Blend會自動幫你產生事件處理常式,例如要產生名為button1的Button控制項之Click事件處理常式,只要輸入「button1.Click+=」按Tab鍵兩次。

延續上述使用程式繫結的例子,若您在Window加一個Button,然後在其Click事件中加入程式碼修改Label控制項的Content屬性為「abc」。

public partial class Window1 : Window {

public Window1 () {

this.InitializeComponent();

Binding b = new Binding ( );

b.Mode = BindingMode.Default;

b.Source = txt;

b.Path = new PropertyPath ("Text" );

lbl.SetBinding ( Label.ContentProperty , b );

button1.Click+=new System.Windows.RoutedEventHandler(button1_Click);

}

private void button1_Click ( object sender , RoutedEventArgs e ) {

lbl.Content = "abc";

}

}

當你執行此程式在文字方塊中輸入的任意文字,馬上會顯示在Label控制項上。若按下按鈕變更Label控制項(Target) 的值為abc時,卻不會影響TextBox控制項 (Source)。就目前此範例而言,將BindingMode 設為BindingMode.Default或將此行程式碼註解執行的行為是一樣的。若修改程式碼,將BindingMode設定為TwoWay,則變更Label控制項 (Target)的內容,會影響到TextBox控制項 (Source)。

若修改程式碼,將BindingMode設定為OneWayToSource,則一執行時,TextBox與Label會顯示空字串,請參考圖6所示。

clip_image012

圖 6:BindingMode設定為OneWayToSource

若按下按鈕變更Label控制項(Target) 的值為abc時,會馬上更新TextBox (source) ,請參考圖7所示。

clip_image014

圖 7:BindingMode設定為OneWayToSource

但後續TextBox (source) 變更時,不修改Label (Target)屬性。

若修改程式碼,將BindingMode設定為BindingMode.OneTime,執行時,第一次會同步,但後續變動TextBox(source)或Label (target) 的屬性都不同步。

若要在Expression Blend中設定Label控制項Content屬性的繫結的方向性,可以選取畫面上Label控制項,然後從屬性視窗,選取Content屬性後方的「Advanced Options」->「Data Binding」->「Show Advanced Properties」,再選取適當的Binding direction,請參考圖8所示。

clip_image016

圖 8:設定Binding direction

Expression Blend 4會自動產生以下語法:

<Label Content = "{Binding Text, ElementName=textBox, Mode=TwoWay}" HorizontalAlignment = "Left" Height = "56" Margin = "64,88,0,0" VerticalAlignment = "Top" Width = "152" />

您也可以手動改寫語法,搭配Path設定:

<Label Content = "{Binding ElementName=textBox, Path=Text, Mode=TwoWay}" HorizontalAlignment = "Left" Height = "56" Margin = "64,88,0,0" VerticalAlignment = "Top" Width = "152" />

再談繫結方向性-OneWayToSource

由於Source的屬性不見得是一個依存屬性(Dependency Properties),但Target屬性必需是一個Dependency屬性,因此若Source屬性是一個自訂的類別,你希望Target屬性變動時,能夠更新Source屬性,那麼使用OneWayToSource將是一個不錯的選擇。

例如以下XAML片段定義一個TextBox、Button與Label:

<TextBox Height = "22" HorizontalAlignment = "Left" Margin = "10,10,0,0" Name = "txt" Text = "TextBox" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "152" />

<Button Content = "Get" Height = "23" HorizontalAlignment = "Left" Margin = "12,54,0,0" Name = "button1" VerticalAlignment = "Top" Width = "75" Click = "button1_Click" />

<Label Content = "Label" Height = "25" HorizontalAlignment = "Left" Margin = "12,94,0,0" Name = "lbl" VerticalAlignment = "Top" Width = "152" />

我們定義一個MyDataClass類別當做是Source,因為類別中沒有依存屬性,所以它不能夠為Target。目前MyDataClass類別中包含一個MyProperty屬性,此屬性是一個一般的屬性不是依存屬性(Dependency Property)。

public class MyDataClass {

public string MyProperty { get; set; }

}

}

在視窗程式碼中,宣告並建立MyDataClass物件,然後在Window建構函式中,利用程式碼,初始化MyProperty的值為123後,設定MyDataClass的MyProperty屬性繫結到TextBox的Text屬性:

public partial class Window1 : Window {

MyDataClass sourceClass = new MyDataClass ( );

public Window1 ( ) {

InitializeComponent ( );

sourceClass.MyProperty = "123";

Binding b = new Binding ( );

b.Mode = BindingMode.OneWayToSource;

b.Source = sourceClass ;

this.DataContext = sourceClass;

txt.SetBinding ( TextBox.TextProperty , "MyProperty" );

}

private void button1_Click ( object sender , RoutedEventArgs e ) {

lbl.Content = sourceClass.MyProperty;

}

}

則當執行時,文字方塊的內容變動後, MyDataClass的MyProperty屬性值也會跟著變動,你可以按下按鈕,取出MyProperty值顯示在Label上,這個值和文字方塊的內容是一樣的。

自訂類別繫結

若要將控制項的屬性繫結到自訂類別的屬性,你可以指定Binding物件的Source屬性為自訂類別的實體。例如XAML中包含一個Grid,Grid中有一個Label。

<Grid x:Name="LayoutRoot" >

<Label HorizontalAlignment= " Left" Height = "25" Margin = "8,8,0,0" VerticalAlignment = "Top" Width = "152" x:Name = "lbl" d:LayoutOverrides = "VerticalAlignment" />

</Grid>

首先在專案中先新增物件資料來源。從Expression Blend「Data」視窗點選最右方「Create Data Source」圖示,然後選取「Create Object Data Source」,請參考圖9所示。

clip_image018

圖 9:Create Object Data Source

在「Create Object Data Source」視窗中選取自訂的MyDataClass類別,請參考圖10所示。

clip_image020

圖 10:定義資料來源

在Expression Blend 4中選取畫面上Grid控制項,然後從屬性視窗點選DataContext屬性後方的「Advanced Options」->「Data Binding」,設定Data Field頁Data Sources為MyDataClassDataSource,請參考圖11所示:

clip_image022

圖 11:設定Data Field頁Data Sources為MyDataClassDataSource

在Expression Blend中選取畫面上Label控制項,然後從屬性視窗,選取Content屬性後方的「Advanced Options」->「Data Binding」,設定Data Field頁Data Sources為MyMyDataClassDataSource;Fields設定為MyProperty,請參考圖12所示:

clip_image024

圖 12:Fields設定為MyProperty

Expression Blend 4會自動在根項目(Grid)設定DataContext屬性, Grid中的Label控制項就會自動繼承這個設定,因此Label控制項不需要設Source,直接設定屬性名稱即可。最後修改Expression Blend產生的MyDataClass定義,手動設定MyProperty值為「This is my data class」字串:

<local:MyDataClass MyProperty = "This is my data class" x:Key = "MyDataClassDataSource" d:IsDataSource = "True"/>

目前的XAML看起來如下:

<Window xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:wpfDataBinding" mc:Ignorable = "d"

x:Class = "wpfDataBinding.__BindToClass" x:Name = "Window" Title = "__BindToClass" Width = "301" Height =v"143">

<Window.Resources>

<local:MyDataClass MyProperty = "This is my data class" x:Key = "MyDataClassDataSource" d:IsDataSource = "True"/>

</Window.Resources>

<Grid x:Name = "LayoutRoot" Height = "97" Width = "263" DataContext = "{Binding Mode=OneWay, Source={StaticResource MyDataClassDataSource}}" >

<Label HorizontalAlignment = "Left" Height = "25" Margin = "8,8,0,0" VerticalAlignment = "Top" Width = "152" x:Name = "lbl" d:LayoutOverrides = "VerticalAlignment" Content = "{Binding MyProperty}" />

</Grid>

</Window>

此時若執行應用程式,Label控制項便會顯示「This is my data class」字串。當然您也可以直接修改繫結語法,使用Binding物件的Path屬性來設定繫結:

<Label Content = "{Binding Path=MyProperty,Source={StaticResource MyDataClassDataSource}}" HorizontalAlignment = "Left" Height = "25" Margin = "8,47,0,0" VerticalAlignment = "Top" Width = "152" x:Name = "lbl2" />

若上例的根項目Grid的DataContext屬性直接繫結到MyDataClass的MyProperty屬性,則Grid中的Label控制項子項目的繫結語法便可以簡寫為「{Binding}」:

<Grid x:Name = "LayoutRoot" Height = "97" Width = "263" DataContext = "{Binding Source={StaticResource MyDataClassDataSource}, Path=MyProperty}">

<Label Content = "{Binding}" HorizontalAlignment = "Left" Height = "25" Margin = "8,8,0,0" VerticalAlignment = "Top" Width = "152" x:Name = "lbl" d:LayoutOverrides = "VerticalAlignment" />

</Grid>

觸發器(Trigger)

檢視以下的範例,假設目前WPF應用程式中視窗內包含兩個TextBox控制項(textBox1、textBox2):

<Grid x:Name = "LayoutRoot" Height = "202" Width = "377">

<TextBox Height = "23" HorizontalAlignment = "Left" Margin = "91,29,0,0" Name = "textBox1" VerticalAlignment = "Top" Width="120" />

<TextBox Text = "{Binding Text, ElementName=textBox1, Mode=TwoWay}" Height = "23" HorizontalAlignment = "Left" Margin = "90,96,0,0" Name = "textBox2" VerticalAlignment = "Top" Width = "120" />

</Grid>

當你執行程式若更改第一個TextBox控制項的內容,第二個TextBox控制項會馬上跟著變動;但若倒過來更改第二個TextBox控制項的內容,卻不會更新第一個TextBox控制項,要等到第二個TextBox控制項 失去焦點(LostFocus)時,才會更新內容。這是因為預設在WPF OneWay與TwoWay模式下,若Source變更,Target會馬上更新。若在TwoWay以及OneWayToSource模式下,若Target更新,WPF會參考Binding物件UpdateSourceTrigger屬性的設定來決定該如何更新。

UpdateSourceTrigger屬性可以設定為:

1. PropertyChanged:當Target屬性變動時,就會更新Source屬性值

2. LostFocus:當Target失去焦點時,更新Source屬性值。

3. Explicit:當應用程式呼叫BindingExpression物件的UpdateSource 方法時更新Source屬性值

大部分控制項依存屬性相關聯的Binding物件其UpdateSourceTrigger屬性預設值是PropertyChanged,但TextBox控制項的Text屬性的預設值是LostFocus,因此Target的TextBox控制項內容變動時,要等到失去焦點時,才會更新Source的TextBox控制項。您可以變更UpdateSourceTrigger屬性值為PropertyChanged來變更這個行為。若想要取得控制項依存屬性UpdateSourceTrigger的預設值,可以利用依存屬性(DependencyProperty)的GetMetadata方法,參考以下程式碼:

var defaultMetadata = TextBox.TextProperty.GetMetadata ( typeof ( TextBox ) );

var df = ( ( FrameworkPropertyMetadata ) defaultMetadata ).DefaultUpdateSourceTrigger.ToString ( );

MessageBox.Show ( df );

我們可以利用Expression Blend來設定UpdateSourceTrigger,例如選取畫面上第二個TextBox控制項(textBox2),然後從屬性視窗,選取Text屬性後方的「Advanced Options」->「Data Binding」->「Show Advanced Properties」,將「Update source when」設定為PropertyChanged,請參考圖13所示。

clip_image026

圖 13:Update source when設定為PropertyChanged

Microsoft Expression Blend 4會自動建立UpdateSourceTrigger的繫結語法:

<Grid x:Name = "LayoutRoot" Height = "202" Width = "377">

<TextBox Height = "23" HorizontalAlignment = "Left" Margin = "91,29,0,0" Name = "textBox1"

VerticalAlignment = "Top" Width = "120" />

<TextBox Text = "{Binding Text, ElementName=textBox1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height = "23" HorizontalAlignment = "Left" Margin = "90,96,0,0" Name = "textBox2" VerticalAlignment = "Top" Width = "120" />

</Grid>

執行程式更改第一個TextBox控制項的內容,第二個會變動,更改第二個TextBox控制項確第一個也會馬上更新。

使用觸發器明確地更新

假設目前WPF應用程式中視窗內包含兩個TextBox控制項(textBox1、textBox3)以及一個按鈕,若TextBox控制項(textBox3) UpdateSourceTrigger設為Explicit:

<TextBox Height = "23" HorizontalAlignment = "Left" Margin = "91,29,0,0" Name = "textBox1" VerticalAlignment = "Top" Width = "120" />

<TextBox Text = "{Binding Text, ElementName=textBox1, Mode=TwoWay, UpdateSourceTrigger=Explicit}" HorizontalAlignment = "Left" Margin = "91,0,0,43" Name = "textBox3" Width = "120" Height = "23" VerticalAlignment = "Bottom" />

<Button Content = "Update" HorizontalAlignment = "Right" Margin = "0,0,49,43" Width = "80" RenderTransformOrigin = "0.6,1.8" Height = "40" VerticalAlignment = "Bottom" Click = "Button_Click" />

若要利用程式明確地進行更新動作,您可以在Button的Click事件中呼叫Target的GetBindingExpression方法,取得BindingExpression物件,然後叫用UpdateSource方法明確地更新。

private void Button_Click ( object sender , RoutedEventArgs e ) {

BindingExpression be = textBox3.GetBindingExpression ( TextBox.TextProperty);

be.UpdateSource ( );

}

在這種情況下,執行時要按下按鈕,透過程式來進行更新作業。

實作INotifyPropertyChanged介面

自訂類別的屬性變更時,可以透過實作INotifyPropertyChanged介面來完成主動通知。例如前文提及的MyDataClass類別範例,因其BindingMode設定為OneWayToSource,後續變更MyDataClass類別MyProperty值時,並不會自動更新到TextBox控制項,我們可以利用INotifyPropertyChanged介面來達到這個目地。

為了方便說明,我們設計一個MyDataClass2類別, 並實作INotifyPropertyChanged介面,此介面只定義一個PropertyChanged事件。當MyProperty屬性變更時,便觸發PropertyChanged事件,將異動的屬性名稱包裝在PropertyChangedEventArgs物件中丟出。

public class MyDataClass2 : INotifyPropertyChanged {

public event PropertyChangedEventHandler PropertyChanged;

private string _MyProperty="";

public string MyProperty {

get { return _MyProperty; }

set {

if ( _MyProperty != value ) {

_MyProperty = value;

PropertyChanged ( this , new PropertyChangedEventArgs ( "MyProperty" ) );

}

}

}

}

假設目前XAML如下,包含一個TextBox、Label與Button:

<Grid Height = "140" Margin = "10,10,90,111" Width = "178">

<TextBox Height = "22" HorizontalAlignment = "Left" Margin = "10,10,0,0" Name = "txt" Text = "TextBox" TextWrapping = "Wrap" VerticalAlignment = "Top" Width = "152" />

<Button Click = "button1_Click" Content = "Set" Height = "23" HorizontalAlignment = "Left" Margin = "12,54,0,0" Name = "button1" VerticalAlignment = "Top" Width = "75" />

<Label Content = "Label" Height = "25" HorizontalAlignment = "Left" Margin = "12,94,0,0" Name = "lbl" VerticalAlignment = "Top" Width = "152" />

</Grid>

修改與XAML檔案相關聯的程式碼,於建構函式中設定繫結,然後在按鈕的Click事件中變更MyProperty屬性值。

public partial class Window1 : Window {

MyDataClass2 sourceClass = new MyDataClass2 ( );

public Window1 ( ) {

InitializeComponent ( );

Binding b = new Binding ( );

b.Mode = BindingMode.OneWayToSource;

b.Source = sourceClass;

this.DataContext = sourceClass;

txt.SetBinding ( TextBox.TextProperty , "MyProperty" );

}

private void button1_Click ( object sender , RoutedEventArgs e ) {

sourceClass.MyProperty = "123";

lbl.Content = sourceClass.MyProperty;

}

}

執行程式,當按下畫面上按鈕修改MyProperty內容時,其值會自動更新到TextBox控制項的Text屬性。

移除資料繫結

若要移除資料繫結功能,可以叫用BindingOperations的ClearBinding刪除一個Binding,或叫用ClearAllBindings方法刪除所有Binding。例如想要刪除名稱為lbl的Label控制項Content屬性的資料繫結功能,您可以使用以下ClearBinding方法:

BindingOperations.ClearBinding ( lbl , Label.ContentProperty );

或者是使用ClearAllBindings方法:

BindingOperations.ClearAllBindings ( lbl );

總結

資料繫結(Data binding)是建立兩個屬性之間的連線的過程。WPF資料繫結模型提供一個簡單的方式,讓應用程式能夠與資料互動。你可以將WPF UI Element繫結到多種不同的資料來源,像是自訂的商業類別,或定義在XAML之中的物件。

使用OneWay與TwoWay兩種繫結模式的Binding Source必需實作INotifiPropertyChanged介面來偵測變動。若沒有指定Binding物件的Path屬性,預設將會繫結到整個物件。

Tags:

Expression Blend | NET 開發 | WPF | 許薰尹Vivid Hsu | .NET Magazine國際中文電子雜誌

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List