此外值得一提,某些從規模較大的公司過來面試的資深工程師,他們提到公司內部在撰寫 UserControl 時,習慣將這些元件打包成 dll 或是 msi 的檔案,再將此 dll 或 msi 提供給其他工程師以匯入 Visual Studio 的 ToolBox 的方式,即可方便其他工程師以 Drag and drop 的方式使用這些元件。而其實在開啟專案時選擇 UserControl 專案,即可直接編譯出可供匯入 ToolBox 的檔案,在 Visual Studio 的 ToolBox 上點選右鍵,選擇工具即可匯入,如果還是提供元件程式碼給其他 member 的人,不妨試試這種方式讓新的專案更乾淨。
開始這次的紀錄的重點,假設今天我們做了一個 UserControl 是一個可以指定背景色,且可以輸入 Header 與 Text 的元件,最容易的方式可以這麼寫
XAML
<UserControl x:Class="WpfApplication1.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.Background>
<SolidColorBrush Color="{Binding BackgroundColor}"/>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Padding="10,10,10,0"
Text="{Binding Header}"
FontSize="30"
Foreground="#333333"/>
<TextBlock Grid.Row="1"
Padding="10"
Text="{Binding ContentText}"
FontSize="18"
Foreground="#666666"
TextWrapping="Wrap"/>
</Grid>
</UserControl>
C# Code
public partial class MyUserControl : UserControl, INotifyPropertyChanged
{
public MyUserControl()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value))
{
return false;
}
storage = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
return true;
}
private String backgroundColor;
public String BackgroundColor
{
get
{
return backgroundColor;
}
set
{
SetProperty(ref backgroundColor, value, "BackgroundColor");
}
}
private String contextText;
public String ContentText
{
get
{
return contextText;
}
set
{
SetProperty(ref contextText, value, "ContentText");
}
}
private String header;
public String Header
{
get
{
return header;
}
set
{
SetProperty(ref header, value, "Header");
}
}
}
在使用這個 MyUserControl 時的方法如下
XAML
<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"
Title="MainWindow" Height="350" Width="525">
<StackPanel Margin="10">
<local:MyUserControl Width="200" Height="120"
BackgroundColor="#00AED8"
Header="I'm Header 1"
ContentText="Hello World, this is my first control!"/>
<local:MyUserControl Width="200" Height="120"
BackgroundColor="#FFA0A0"
Header="I'm Header 2"
ContentText="Hello World, this is my second control!"/>
</StackPanel>
</Window>
如果使用這個元件的開發者想使用 MVVM 的方式來撰寫時,他可能會想把 Header 的值改成 {Binding Header1} 甚至將這兩些 local:MyUserControl 擺到 ListBox 中 Bind 一個 List 屬性,這時 XAML 內容在編譯時會被錯誤提示為 A 'Binding' can only be set on a DependencyProperty of a DependencyObject. 故以下示範將這個 MyUserControl 改寫為可 Binding 的元件
MyUserControl.xaml
<UserControl x:Class="WpfApplication1.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="root"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.Background>
<SolidColorBrush Color="{Binding BackgroundColor, ElementName=root}"/>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Padding="10,10,10,0"
Text="{Binding Header, ElementName=root}"
FontSize="30"
Foreground="#333333"/>
<TextBlock Grid.Row="1"
Padding="10"
Text="{Binding ContentText, ElementName=root}"
FontSize="18"
Foreground="#666666"
TextWrapping="Wrap"/>
</Grid>
</UserControl>
MyUserControl.xaml.cs
public partial class MyUserControl : UserControl, INotifyPropertyChanged
{
public MyUserControl()
{
InitializeComponent();
//DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value))
{
return false;
}
storage = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
return true;
}
private String backgroundColor;
public String BackgroundColor
{
get
{
return backgroundColor;
}
set
{
SetProperty(ref backgroundColor, value, "BackgroundColor");
}
}
public static readonly DependencyProperty ContentTextProperty =
DependencyProperty.Register("ContentText",
typeof(String), typeof(MyUserControl), null);
public String ContentText
{
get
{
return (String)GetValue(ContentTextProperty);
}
set
{
SetValue(ContentTextProperty, value);
}
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header",
typeof(String), typeof(MyUserControl), null);
public String Header
{
get
{
return (String)GetValue(HeaderProperty);
}
set
{
SetValue(HeaderProperty, value);
}
}
}
使用 MyUserControl 的 MainWindow 即可使用 Binding 或原本固定的字串任一種方式,例如
MainWindow.xaml
<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"
Title="MainWindow" Height="350" Width="525">
<StackPanel Margin="10">
<local:MyUserControl Width="200" Height="120"
BackgroundColor="#00AED8"
Header="{Binding Header1}"
ContentText="{Binding ContentText}"/>
<local:MyUserControl Width="200" Height="120"
BackgroundColor="#FFA0A0"
Header="{Binding Header2}"
ContentText="Hello World, this is my second control!"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public String Header1
{
get
{
return "Header 1";
}
}
public String Header2
{
get
{
return "Header 2";
}
}
public String ContentText
{
get
{
return "Hello World, this is my first control!";
}
}
}
如果改寫完成後發生 Binding 的資料沒有如期出現的狀況,檢查一下是否犯了以下幾種常見錯誤
1. Control 的 DenpendencyProperty 參數是否有給正確的值及 Property 名稱
2. Control 原先建構式中的 DataContext = this; 必須拿掉
3. Control 的 XAML 中是否有在需支援的屬性加上 ElementName=root 與 Control 本身是否命名為 root 或其他名稱
以上三個常犯錯誤的發生原因通常是對 Binding 的階層關係不夠熟悉,建議再將文件 ( Data Binding Overview ) 詳細的讀過,要進行更進階的操作如新增 Model 的階層時才有辦法撰寫出正確的結果。
沒有留言:
張貼留言