UWP(Universal Windows Platform)介紹 — 快速上手

1. Xaml

1.1. XAML 簡介

  • XAML 概觀
  • 主要用於建立可視的 UI 元素.
  • Xaml 的基本語法基於 XML.
  • 宣告一個 namespace 的別名:
xmlns:controls="using:Common.Controls"
  • 使用 namespace 中宣告的類別:
<controls:MyControl />
  • 可重複使用的資源,

    x:Key

<Style x:Key="TextBlock_Style" />
  • 控制項元素的 Name,

x:Name

Xaml:

<MyControl x:Name="myControl" />

C#:

private MyControl myControl;
  • 在地化

    x:Uid

<TextBlock x:Uid="sampleText" />

1.2. 最基礎的控制項 – TextBlock, Button

<Page x:Class="MyPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <TextBlock Text="UWP Introduction" />
        <Button Content="UWP Introduction" Click="Button_Click" />
    </StackPanel>
</Page>

2. MVVM

MVVM 詳細介紹

View <=> ViewModel <=> Model

  • View 盡量只包含 UI 顯示的內容, View 大部分使用 Xaml 語言完成;
  • ViewModel 應盡量不包含業務處理邏輯, 而是透過呼叫 Model 裡的函式完成動作;
  • Model 應盡量包含所有的業務資料和邏輯, 並盡量不依賴 View 和 ViewModel;

3. ViewModel 與 Model 的互動

3.1. ViewModel 操作 Model 的資料

ViewModel:

public class ViewModel
{
    private Model model;
    private ChangeA()
    {
        this.model.A = "A";
    }
}

Model:

public class Model
{
    public string A { get; set; }
    public string B { get; set; }
}

3.2. Model 通知 ViewModel – event

ViewModel:

public class ViewModel
{
private Model model;
    private ChangeA()
    {
        r.BEventArgs += this.Handler;
    }
    private void Handler(object sender, EventArgs e)
    {
        AnyActions();
    }
}

Model:

public delegate void BChangedHandler(object sender, EventArgs e);

public class Model
{
    public string A { get; set; }
    private string _B;
    public string B
    {
        get { return this._B; }
        set
        {
            this._B = value;
            if (BEventArgs != null)
            {
                BEventArgs(this, new EventArgs());
            }
        }
    }
    public event BChangedHandler BEventArgs;
}

4. View 與 ViewModel 的互動

4.1. 資料繫結

4.1.1. 繫結在 ViewModel

View:

<TextBlock Text={Binding SampleText} />

ViewModel:

public string SampleText { get; set; }

4.1.2. 繫結在其它 Control

View:

<TextBlock x:Name="TextBlock1" Text="SampleText" />
<Button Content="{Binding ElementName=TextBlock1, Path= Text}" />

4.1.3. 指定 DataContext

public ViewModelClass ViewModel { get; set; }

...
SpecifiedControl.DataContext = ViewModel;
...

5. (View 與 ViewModel) 實作訊息通知 – INotifyPropertyChanged

當 SampleText 發生變化時, 會通知到繫結在該 property 的 DependencyProperty

public class MyControlViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void Notify(string propName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }

        private string _sampleText;
        public string SampleText
        {
            get
            {
                return _sampleText;
            }
            set
            {
                _sampleText = value;
                Notify(nameof(SampleText));
            }
        }
    }

或者繼承 ViewModelBase

public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void Notify(string propName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }

6. (View 與 ViewModel) ListView 繫結到實作了訊息通知的來源 – ObservableCollection

ListView

在 ListView 中實作繫結到具有訊息通知的 ItemSource

View:

<ListView ItemsSource="{Binding Items}">

ViewModel:

public ObservableCollection<Recording> Items { get; set; }
  • ObservableCollection 只在 Item 新增\移除, 整個列表重新整理時才會產生訊息通知;
  • 如果需要在 item Recording 的內容發生變化時通知介面, 需要由 Recording 實作 INotifyPropertyChanged.

7. (View) ListView 的 Item 範本

7.1. DataTemplate

View:

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemTemplate>
        <DataTemplate DataType="local:Recording">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding A}" />
                <TextBlock Text="{Binding B}" />
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

ViewModel:

public ObservableCollection<RecordingViewModel> Items { get; set; }

public class RecordingViewModel : INotifyPropertyChanged
{
    ...
    實作 INotifyPropertyChanged
    ...

    private Recording _recording;

    public string A
    {
        get
        {
            return this._recording.A;
        }
        set
        {
            this._recording.A = value;
            Notify(nameof(A));
        }
    }

    public string B { get; set; } = this._recording.B;

    public RecordingViewModel (Recording recording)
    {
        this._recording = recording;
    }
}

Model:

public class Recording
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    ... ...
}

比較:

分離 ViewModel/Model

7.2. UserControl

7.2.1. DependencyProperty

View:

<control:MyControl Text="App is on searching" IsSearching="{Binding ViewModel.IsSearching}" />

ViewModel:

public class MyControl
{
...
public static readonly DependencyProperty IsSearchingProperty =
    DependencyProperty.Register
    (
        "IsSearching", typeof(Boolean),
        typeof(MyControl), null
    );

public bool IsSearching
{
    get { return (bool)GetValue(IsSearchingProperty); }
    set { SetValue(IsSearchingProperty, value); }
}
...
}

8. (ViewModel 與 Model) 資料轉換 – IValueConverter

可以使用IValueConverter來進行 ViewModel 與 Model 之間的資料轉換.

public class ShowAllButtonVisibilityConverter:IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is IList)
            {
                int count = (value as IList).Count;
                if (count > 3)
                {
                    return Windows.UI.Xaml.Visibility.Visible;
                }
            }
            return Windows.UI.Xaml.Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            if (value is IList)
            {
                int count = (value as IList).Count;
                if (count > 3)
                {
                    return Windows.UI.Xaml.Visibility.Visible;
                }
            }
            return Windows.UI.Xaml.Visibility.Collapsed;
        }

9. (View) 修改 Control 的樣式

9.1. 在 Control 裡自訂 Style

View:

<TextBlock Foreground="Red" Text="SampleText" />

9.2. 使用統一的 Style – ResourceDictionary

View:

<Window.Resources>
    <ResourceDictionary>
        <Style TargetType="TextBlock" x:Key="ImportantText">
            <Setter Property="Foreground" Value="Red" />
        </Style>
    </ResourceDictionary>
</Window.Resources>
...
<TextBlock Text="SampleText" Style={StaticResource ImportantText} />

9.3. 使用 ThemeResource

View:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <ResourceDictionary x:Key="Light">
                <Style TargetType="TextBlock" x:Key="ImportantText">
                    <Setter Property="Foreground" Value="Red" />
                </Style>
            </ResourceDictionary>

            <ResourceDictionary x:Key="Dark">
                <Style TargetType="TextBlock" x:Key="ImportantText">
                    <Setter Property="Foreground" Value="Yellow" />
                </Style>
            </ResourceDictionary>

            <ResourceDictionary x:Key="HighContrast">
                <Style TargetType="TextBlock" x:Key="ImportantText">
                    <Setter Property="Foreground" Value="Black" />
                </Style>
            </ResourceDictionary>
        </ResourceDictionary.ThemeDictionaries>
    </ResourceDictionary>
</Window.Resources>
...
<TextBlock Text="SampleText" Style={ThemeResource ImportantText} />

10. 使用 Panel

10.1. Grid

特點:

  • 預設 Height/Width 等於父級元素;

建議:

  • 在 Grid 中定義子元素的版面配置大小;
  • 以比例顯示;

10.2. StackPanel

特點:

  • 可以超越父元素邊界;
  • Height 或 Width 隨 Panel 內的元素變化;

建議:

  • 靈活使用 Padding 和比例;
  • 在父級元素給 StackPanel 的 Width 或 Height 一個值, 以防止 Control 越界;

11. (View) 調適式 UI (Adaptive UI)

  • 使用 VisualStateManager.VisualStateGroup
<VisualStateGroup>
    <VisualState x:Name="WideLayout">
        <VisualState.StateTriggers>
            <AdaptiveTrigger x:Name="WideLayoutTrigger" MinWindowWidth="1280" />
        </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="SystemUpdateSideGrid.Width" Value="800" />
            <Setter Target="SystemUpdateSideGrid.Grid.Row" Value="0" />
        </VisualState.Setters>
    </VisualState>

    <VisualState x:Name="MidLayout">
        <VisualState.StateTriggers>
            <AdaptiveTrigger x:Name="MidLayoutTrigger" MinWindowWidth="700" />
        </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="SystemUpdateSideGrid.Width" Value="400" />
            <Setter Target="SystemUpdateSideGrid.Grid.Row" Value="1" />
        </VisualState.Setters>
    </VisualState>
    ...
</VisualStateGroup>

12. 版面配置原則

  • 不顯式設定元素的尺寸;
  • 不使用螢幕座標指定元素的位置;
  • 容器內子元素共享可用的空間;
  • 可巢狀的版面配置容器;

13. 在地化

  • 使用 x:Uid, 元素的唯一標記符
    <TextBlock x:Uid="S_TextBlock1" />
  • 使用 Resources File
    <data name="S_TextBlock1.Text" xml:space="preserve">
        <value>Sample text</value>
    </data>

14. 命名慣例

大駝峰 big camel-case: firstName

小駝峰 little camel-case: FirstName

  • Class: 大駝峰式
  • Property: 大駝峰式
  • Field: 小駝峰式 with prefix “_”
  • Control in Xaml: 小駝峰式

15. 注意

  • 謹慎重新命名 ViewModel 中的 Property, 因為 Xaml 中的 Binding 名稱不會跟隨重構;