顯示具有 程式設計 標籤的文章。 顯示所有文章
顯示具有 程式設計 標籤的文章。 顯示所有文章

2015年7月4日 星期六

WPF學習筆記 - 16 Command

一般我們針對control比較常會使用event,例如有個button,
點了button後要執行什麼樣的動作,用event就可以完成。

但有時候某些動作比較特別,像是有個貼上的按鈕、crtl+v、toolbar有貼上的功能…
這種情況下我們就能使用command來完成,.net framework已經提供了許多的command供我們使用

例如,一個簡單的cut、paste功能

<DockPanel>
        <WrapPanel DockPanel.Dock="Top" Margin="3">
            <Button Command="ApplicationCommands.Cut" CommandTarget="{Binding ElementName=txtEditor}" Width="60">_Cut</Button>
            <Button Command="ApplicationCommands.Paste" CommandTarget="{Binding ElementName=txtEditor}" Width="60" Margin="3,0">_Paste</Button>
        </WrapPanel>
        <TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>

當有選取文字時,cut才會變enable…之類的動作,WPF都已經幫我們設計好了。

<Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand_Executed" CanExecute="NewCommand_CanExecute" />
</Window.CommandBindings>

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button Command="ApplicationCommands.New">New</Button>
</StackPanel>

設計一個ApplicationCommands.New的command,注意到有兩個重要的method,
Executed - 用來實作這個command要如何執行
CanExecuted - 用來實作這個command現在允不允許被執行

private void NewCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
                        e.CanExecute = true; // 這裡設成ture,所以command永遠都是可以被執行的狀態
}

private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
                        MessageBox.Show("The New command was invoked");
}

當然我們也可以自行設計command

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:self="clr-namespace:WpfApplication1"
        Title="Say Hello" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="self:CustomCommands.Exit" CanExecute="ExitCommand_CanExecute" Executed="ExitCommand_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="File">
                <MenuItem Command="self:CustomCommands.Exit" />
            </MenuItem>
        </Menu>
        <StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Button Command="self:CustomCommands.Exit">Exit</Button>
        </StackPanel>
    </Grid>
</Window>

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ExitCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void ExitCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }
    }

    public static class CustomCommands
    {
        // 這裡定義了Exit command,並且按下Alt + F4也可以執行Exit的動作
        public static readonly RoutedUICommand Exit = new RoutedUICommand
                (
                        "Exit",
                        "Exit",
                        typeof(CustomCommands),
                        new InputGestureCollection()
                                {
                                        new KeyGesture(Key.F4, ModifierKeys.Alt)
                                }
                );
    }

2015年5月30日 星期六

WPF學習筆記 - 15 Custom dialog

按下Enter name...後跳出自訂的dialog,輸入name後,會把值帶到lblName裡

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">

        <TextBlock>Hello, world. My name is:</TextBlock>

        <TextBlock Name="lblName" Margin="0,10" TextAlignment="Center" FontWeight="Bold">[No 

name entered]</TextBlock>

        <Button Name="btnEnterName" Click="btnEnterName_Click">Enter name...</Button>

</StackPanel>

private void btnEnterName_Click(object sender, RoutedEventArgs e)

                {

                        Dialogs inputDialog = new Dialogs("Please enter your name:", "Elliott Chen");

                        if(inputDialog.ShowDialog() == true)

                                lblName.Text = inputDialog.Answer;

                }

新增Dialogs.xaml,

拿掉Hight和Width屬性,改為SizeToContent="WidthAndHeight",讓視窗的大小自動調整,

WindowStartupLocation="CenterScreen"(或CenterOwner),屬性設定視窗被打開時的起始位置,

另外在Cancel button加上IsCancel="True",確保使用者按下此button或Esc會關閉視窗,

IsDefault="True",會把focus指給此button,並按下Enter可以執行此button

<Window x:Class="WpfApplication1.Dialogs"

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

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

        Title="Input" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen"

        ContentRendered="Window_ContentRendered"> // 這裡取消了Hight和Width,新增了其他的屬性

    <Grid Margin="15">

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="Auto" />

            <ColumnDefinition Width="*" />

        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>

            <RowDefinition Height="Auto" />

            <RowDefinition Height="Auto" />

            <RowDefinition Height="Auto" />

        </Grid.RowDefinitions>

        <Label Name="lblQuestion" Grid.Column="1">Question:</Label>

        <TextBox Name="txtAnswer" Grid.Column="1" Grid.Row="1" MinWidth="250">Answer</TextBox>

        <WrapPanel Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Right" Margin="0,15,0,0">

            <Button IsDefault="True" Name="btnDialogOk" Click="btnDialogOk_Click" MinWidth="60" 

Margin="0,0,10,0">_Ok</Button>

            <Button IsCancel="True" MinWidth="60">_Cancel</Button>

        </WrapPanel>

    </Grid>

</Window>

backend


在constructor多加兩個參數,可以傳入問題和回答,

btnDialogOk_Click method只是傳回一個result,告知WPF,

需要注意的是Window_ContentRendered事件,自動選取txtAnswer的內容,並focuse,

最後的Answer method將txtAnswer裡內容傳回去

// 與一般的作法幾乎一模一樣

public partial class Dialogs : Window

    {

        public Dialogs(string question, string defaultAnswer = "")

        {

            InitializeComponent();

            lblQuestion.Content = question;

            txtAnswer.Text = defaultAnswer;

        }

        private void btnDialogOk_Click(object sender, RoutedEventArgs e)

        {

            this.DialogResult = true;

        }

        private void Window_ContentRendered(object sender, EventArgs e)

        {

            txtAnswer.SelectAll();

            txtAnswer.Focus();

        }

        public string Answer

        {

            get { return txtAnswer.Text; }

        }

    }

WPF學習筆記 - 14 Dialogs : MessageBox, OpenFileDialog, SaveFileDialog

MessageBox


MessageBox.Show("Content", "Title");

MessageBox.Show("This MessageBox has extra options.\n\nHello, world?", "My App", 

MessageBoxButton.YesNoCancel);

MessageBoxButton屬性:

OK

OKCancel

YesNoCancel

YesNo

接收使用者的reponse來執行不同的動作

MessageBoxResult result = MessageBox.Show("Would you like to greet the world with a \"Hello, 

world\"?", "My App", MessageBoxButton.YesNoCancel);

switch(result)

{

        case MessageBoxResult.Yes:

                MessageBox.Show("Hello to you too!", "My App");

                break;

        case MessageBoxResult.No:

                MessageBox.Show("Oh well, too bad!", "My App");

                break;

        case MessageBoxResult.Cancel:

                MessageBox.Show("Nevermind then...", "My App");

                break;

}

加上icon

MessageBox.Show("Hello, world!", "My App", MessageBoxButton.OK, MessageBoxImage.Information);

MessageBoxImage屬性:

Asterisk

Error

Exclamation

Hand

Information

None

Question

Stop

Warning

預設的選擇(預設選no)

MessageBox.Show("Hello, world?", "My App", MessageBoxButton.YesNo, MessageBoxImage.Question, 

MessageBoxResult.No);

OpenFileDialog


一個簡單的範例,選取txt後會把內容show在txtEditor裡

<DockPanel Margin="10">

        <WrapPanel HorizontalAlignment="Center" DockPanel.Dock="Top" Margin="0,0,0,10">

            <Button Name="btnOpenFile" Click="btnOpenFile_Click">Open file</Button>

        </WrapPanel>

        <TextBox Name="txtEditor" />

</DockPanel>

private void btnOpenFile_Click(object sender, RoutedEventArgs e)

{

                        OpenFileDialog openFileDialog = new OpenFileDialog(); 

// 設定Filter,指定只能開啟特定的檔案 

openFileDialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";

                        // 選擇圖片的Filter

                        // openFileDialog.Filter = "Image files (*.png;*.jpeg)|*.png;*.jpeg|All files (*.*)|*.*";

                        if(openFileDialog.ShowDialog() == true)

                                txtEditor.Text = File.ReadAllText(openFileDialog.FileName);

}

設定預設從哪個目錄開始

openFileDialog.InitialDirectory = @"c:\temp\";

openFileDialog.InitialDirectory =

Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

https://msdn.microsoft.com/en-us/library/system.environment.specialfolder.aspx

開啟複數檔案

<DockPanel Margin="10">

        <WrapPanel HorizontalAlignment="Center" DockPanel.Dock="Top" Margin="0,0,0,10">

            <Button Name="btnOpenFile" Click="btnOpenFiles_Click">Open files</Button>

        </WrapPanel>

        <ListBox Name="lbFiles" />

</DockPanel>

private void btnOpenFiles_Click(object sender, RoutedEventArgs e)

                {

                        OpenFileDialog openFileDialog = new OpenFileDialog();

                        openFileDialog.Multiselect = true; // 設定可以開多個檔案

                        openFileDialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";

                        openFileDialog.InitialDirectory = 

Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

                        if(openFileDialog.ShowDialog() == true)

                        {

                                foreach(string filename in openFileDialog.FileNames)

                                        lbFiles.Items.Add(Path.GetFileName(filename));

                        }

                }

SaveFileDialog


簡單的範例

<DockPanel Margin="10">

        <WrapPanel HorizontalAlignment="Center" DockPanel.Dock="Top" Margin="0,0,0,10">

            <Button Name="btnSaveFile" Click="btnSaveFile_Click">Save file</Button>

        </WrapPanel>

        <TextBox Name="txtEditor" TextWrapping="Wrap" AcceptsReturn="True" 

ScrollViewer.VerticalScrollBarVisibility="Auto" />

    </DockPanel>

private void btnSaveFile_Click(object sender, RoutedEventArgs e)

                {

                        SaveFileDialog saveFileDialog = new SaveFileDialog();

                        if(saveFileDialog.ShowDialog() == true)

                                File.WriteAllText(saveFileDialog.FileName, txtEditor.Text);

                }

設定Filter

saveFileDialog.Filter = "Text file (*.txt)|*.txt|C# file (*.cs)|*.cs";

設初始目錄

saveFileDialog.InitialDirectory = @"c:\temp\";

2015年5月17日 星期日

WPF學習筆記 - 13 Data binding part 2

即時反應變化

<DockPanel Margin="10">

                <StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">

                        <Button Name="btnAddUser" Click="btnAddUser_Click">Add user</Button>

                        <Button Name="btnChangeUser" Click="btnChangeUser_Click" Margin="0,5">Change 

user</Button>

                        <Button Name="btnDeleteUser" Click="btnDeleteUser_Click">Delete user</Button>

                </StackPanel>

                <ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>

</DockPanel>

XAML非常容易,只是設定三個Button,去呼叫三個method

主要binding的部份在backend處理

// 首先先設定一個全域變數,並且要是ObservableCollection的集合,如果是一般的List無法達成即

時反應

private var users = new ObservableCollection<User>();

// 實作三個method,只是針對集合做簡單的新增、修改、刪除的動作

private void btnAddUser_Click(object sender, RoutedEventArgs e)

                        users.Add(new User() { Name = "New user" });

private void btnChangeUser_Click(object sender, RoutedEventArgs e)

                        if(lbUsers.SelectedItem != null)

                                (lbUsers.SelectedItem as User).Name = "Random Name";

private void btnDeleteUser_Click(object sender, RoutedEventArgs e)

                        if(lbUsers.SelectedItem != null)

                                users.Remove(lbUsers.SelectedItem as User);

// 實作User類別,只繼承至INotifyPropertyChanged

public class User : INotifyPropertyChanged

                private string name;

                public string Name {

                        get { return this.name; }

                        set

                        {

                                if(this.name != value)

                                {

                                        this.name = value;

                                        this.NotifyPropertyChanged("Name");

                                }

                        }

                }

                public event PropertyChangedEventHandler PropertyChanged;

                public void NotifyPropertyChanged(string propName)

                {

                        if(this.PropertyChanged != null)

                                this.PropertyChanged(this, new PropertyChangedEventArgs(propName));

                }

注意,使用INotifyPropertyChanged需要using System.ComponentModel,

使用ObservableCollection需要using System.Collections.ObjectModel


IValueConverter


數值轉換,當你想在backend偷偷的做某些設定,像是CheckBox是以Boolean代表是否勾選,

要客制化為以yes和no代表是否勾選

<Window x:Class="WpfApplication1.MainWindow"

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

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

        xmlns:local="clr-namespace:WpfApplication1" <!-- 需要增加這一行,後面才可以使用locat的

tag,WpfApplication1為backend的namespace -->

        Title="Say Hello" Height="350" Width="525">

    <Window.Resources>

        <local:YesNoToBooleanConverter x:Key="YesNoToBooleanConverter" /> <!-- 新增一個

key="YesNoToBooleanConverter" -->

    </Window.Resources>

    <StackPanel Margin="10">

        <TextBox Name="txtValue" />

        <WrapPanel Margin="0,10">

            <TextBlock Text="Current value is: " />

            <TextBlock Text="{Binding ElementName=txtValue, Path=Text, Converter={StaticResource 

YesNoToBooleanConverter}}"></TextBlock>

        </WrapPanel>

        <CheckBox IsChecked="{Binding ElementName=txtValue, Path=Text, Converter={StaticResource 

YesNoToBooleanConverter}}" Content="Yes" />

    </StackPanel>

</Window>

後面做Binding時,即可設定Converter={StaticResource YesNoToBooleanConverter}},來指定

converter

而在backend需要實作YesNoToBooleanConverter class,並實作Convert() 和 ConvertBack() 兩個

method

public class YesNoToBooleanConverter : IValueConverter

        // YesNo轉boolean

        public object Convert(object value, Type targetType, object parameter, 

System.Globalization.CultureInfo culture)

        {

            switch (value.ToString().ToLower())

            {

                case "yes":

                case "oui":

                    return true;

                case "no":

                case "non":

                    return false;

            }

            return false;

        }

        

        // bool轉YesNo

        public object ConvertBack(object value, Type targetType, object parameter, 

System.Globalization.CultureInfo culture)

        {

            if (value is bool)

            {

                if ((bool)value == true)

                    return "yes";

                else

                    return "no";

            }

            return "no";

        }

StringFormat

如果只是想要更改顯示出來的樣子,而不需要真的更改實際值的話可以使用

<Window x:Class="WpfApplication1.MainWindow"

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

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

        xmlns:system="clr-namespace:System;assembly=mscorlib" <!-- 加這一行才有system參數可用 -->

        Title="Say Hello" Height="350" Width="525" Name="wnd"> <!-- 需多加Name,供後面的TextBox 

binding -->

    <StackPanel Margin="10">

        <TextBlock Text="{Binding ElementName=wnd, Path=ActualWidth, StringFormat=Window width: 

{0:#,#.0}}" />

        <TextBlock Text="{Binding ElementName=wnd, Path=ActualHeight, StringFormat=Window height: 

{0:C}}" />

        <TextBlock Text="{Binding Source={x:Static system:DateTime.Now}, StringFormat=Date: {0:dddd, 

MMMM dd}}" />

        <TextBlock Text="{Binding Source={x:Static system:DateTime.Now}, StringFormat=Time: 

{0:HH:mm}}" />

    </StackPanel>

</Window>

關於

StringFormat=Window height: {0:C}}"

https://msdn.microsoft.com/en-us/library/dwhawy9k.aspx

StringFormat=Date: {0:dddd, MMMM dd}}"

https://msdn.microsoft.com/en-us/library/az4se3k1.aspx

StringFormat=Window width: {0:#,#.0}},

如果不需要"Window width: "可以改為StringFormat={}{0:#,#.0}}

可自行設定ConverterCulture

<TextBlock Text="{Binding Source={x:Static system:DateTime.Now}, ConverterCulture='en-US',

StringFormat=American date: {0:D}}" />

WPF學習筆記 - 12 Data binding part 1

<StackPanel Margin="10">

                <TextBox Name="txtValue" />

                <WrapPanel Margin="0,10">

                        <TextBlock Text="Value: " FontWeight="Bold" />

                        <TextBlock Text="{Binding Path=Text, ElementName=txtValue}" />

                </WrapPanel>

</StackPanel>

透過{Binding Path=Text, ElementName=txtValue},自動把txtValue的值bind到textbox裡

{Binding Path=Text, ElementName=txtValue}也可以寫成{Binding Text, ElementName=txtValue},

因為Path是預設值,後面的Text代表你bind的來源是什麼。

DataContext

<StackPanel Margin="15">

                <WrapPanel>

                        <TextBlock Text="Window title:  " />

                        <TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" Width="150" />

                </WrapPanel>

                <WrapPanel Margin="0,10,0,0">

                        <TextBlock Text="Window dimensions: " />

                        <TextBox Text="{Binding Width}" Width="50" />

                        <TextBlock Text=" x " />

                        <TextBox Text="{Binding Height}" Width="50" />

                </WrapPanel>

</StackPanel>

這裡用了三個bind,第一個textbox bind app的title,

第二、三個textbox分別bind window的寬和高,當window被拖曳,寬高改變時,也會直接顯示到

這兩個textbox裡。

不過這時候還沒有完成bind,你必需在backend加上,

public MainWindow()

{

            InitializeComponent();

            this.DataContext = this;

}

將DataContext設定好,才會生效。

上個例子我們看到了,UpdateSourceTrigger=PropertyChanged這個屬性,

代表當textbox的值被更動時,也會即時更新title,

如果將PropertyChanged改為LostFocus,則會當Focus移走時才反應,這是預設的行為,

如果使用Explicit,則需要明確表明需要更新,才會去更新title。

<WrapPanel>

                        <TextBlock Text="Window title:  " />

                        <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}"

Width="150" />

                        <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0"

Padding="5,0">更新</Button>

</WrapPanel>

backcode

private void btnUpdateSource_Click(object sender, RoutedEventArgs e)

{

                        BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);

                        binding.UpdateSource();

}

當點下"更新"Button時,才會更新title。

2015年5月5日 星期二

WPF學習筆記 - 11 WPF Pannels - Grid, GridSplitter, UniformGrid

Grid


是最複雜的一個Panel,可以切割為許多的rows和columns,
需要設定每個rows和columns的百分比,
當許要複雜的格式,或是組合各個Panel可以使用

切成左右兩邊

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2</Button>
</Grid>

此時兩個Button各會填滿左右兩邊,如果不希望這種情況發生,可以增加屬性來控制

<Button VerticalAlignment="Top" HorizontalAlignment="Center">Button 1</Button>
<Button Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right">Button 2</Button>

建一個3*3的表格

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2</Button>
<Button Grid.Column="2">Button 3</Button>
<Button Grid.Row="1">Button 4</Button>
<Button Grid.Column="1" Grid.Row="1">Button 5</Button>
<Button Grid.Column="2" Grid.Row="1">Button 6</Button>
<Button Grid.Row="2">Button 7</Button>
<Button Grid.Column="1" Grid.Row="2">Button 8</Button>
<Button Grid.Column="2" Grid.Row="2">Button 9</Button>
</Grid>

值得注意的是Width="2*",代表這個control是Width="*"control的兩倍大(* = 1*)

在Grid裡有一種指定大小的方法
1. * - 會隨著其他control的大小,自動變化
2. Auto - 隨著control的內容,自動變化
3. 設定絕對的大小

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2 with long text</Button>
<Button Grid.Column="2">Button 3</Button>
</Grid>

使用ColumnSpan或RowSpan跨欄

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button>Button 1</Button>
<Button Grid.Column="1">Button 2</Button>
<Button Grid.Row="1" Grid.ColumnSpan="2">Button 3</Button>
</Grid>

此為Two Rows、Two Columns的表格,可以發現第二個Row的button整個填滿了整個Row,
因為使用了Grid.ColumnSpan="2"

GridSplitter


可以讓使用者自行用滑鼠拉動,調整大小
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock FontSize="55" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap">Left side</TextBlock>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<TextBlock Grid.Column="2" FontSize="55" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap">Right side</TextBlock>
</Grid>

一個簡單的留言版面

<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label>Name:</Label>
<TextBox Grid.Column="1" Margin="0,0,0,10" />
<Label Grid.Row="1">E-mail:</Label>
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" />
<TextBox Grid.Row="2" Grid.ColumnSpan="2" AcceptsReturn="True" />
</Grid>

UniformGrid


與Grid相同,但每個rows和columns都是相同的size

[泰國] 芭達雅大城八日遊 Day7、8 - 瑪哈泰寺、Wat Ratchaburana、帕蒙空博碧寺、帕席桑碧寺、Hua Ro market、Chantharakasem National Museum、Amporn Department Store

第七天行程,早上吃飯店早餐,算不錯吃,只是有螞蟻,但吃完沒拉,所以應該還好 吃飽後飽嘟嘟車,一小時 200 泰銖,比自己叫還便宜,終於要來世界遺產之旅了 第一站:瑪哈泰寺,應該是大城最有名的景點了,入門票大人 120 ,小孩免費,其實不便宜 大城指標性景點...