Blog chia sẽ kinh nghiệm lập trình và Technical VN. Nơi mà các bạn có thể học tập và tham khảo về những công nghệ mới nhất hiện nay về .NET
Thứ Bảy, 28 tháng 3, 2015
Đối với lập trình hiện đại hiện nay, việc hỗ trợ người dùng tối đa cũng như trừu tượng hóa các công việc đến mức gần gủi và dễ hiểu nhất cho người dùng đang là tiêu chí hàng đầu của các nhà phát triển phần mềm, chúng ta có thể thấy lợi ích từ việc mà các ứng dụng hộ trợ Drag-and-Drop, công việc trở nên đơn giản hơn và chân thật hơn, thao tác kéo thả nói lên nhiều ý nghĩa hơn việc nhấn các Button. Và hôm nay, tôi sẽ hướng dẫn cở bản cho việc Implement Drag-and-Drop trong WPF.
WPF Training - Drag and Drop Trong WPF Part II...
I> Design UI
Công việc rất đơn giản, chúng ta cần chuẩn bị những thứ như sau
- WrapPanel A: nguồn chứa đối tượng - Drag
- WrapPanel B: nguồn nhận đối tượng - Drop
- Ellipse: Đối tượng được drag-drop
Chúng ta Design như sau
[code language="xml"]
<WrapPanel x:Name="_target" Drop="UIElement_OnDrop" AllowDrop="True"
Background="Chartreuse"/>
<WrapPanel x:Name="_source" MouseMove="MouseMove" Grid.Column="2">
[/code]
Trong đó,
- "UIElement_OnDrop" là method sẽ được call khi có đối tượng đang được Drag lên _target Panel
- "MouseMove" là method xử lý cho việc Drag, chúng ta có thể dùng ở nhiều sự kiện khác cũng được như MouseLeftButtonDown="MouseMove".
II> Implement behavior
Chúng ta tiến hành implement cho hai method trên như sau
[code language="csharp"]
#region
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
#endregion
namespace Drag_Drop_Sample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MouseMove(object sender, MouseEventArgs e)
{
// Get current obj
var ellipse = e.Source as Ellipse;
if (ellipse != null && e.LeftButton == MouseButtonState.Pressed)
{
// Send obj to target
DragDrop.DoDragDrop(ellipse,ellipse,
DragDropEffects.Move);
}
}
private void UIElement_OnDrop(object sender, DragEventArgs e)
{
// Get obj
var obj = e.Data.GetData(e.Data.GetFormats()[0]) as UIElement;
// Get source and target Panel
var parent = (obj as FrameworkElement).Parent as Panel;
var sendertarget = sender as Panel;
if (obj != null && sendertarget != null && !sendertarget.Children.Contains(obj))
{
parent.Children.Remove(obj);
sendertarget.Children.Add(obj);
}
}
}
}
[/code]
Kết quả:


Source tại đây, chúc các bạn thành công!
Tuân Phạm
Chủ Nhật, 1 tháng 3, 2015
Để có thể binding được cho một group các RadioButton chúng ta cần một số thủ thuật, giới hạn trong bài này tôi sẽ chia sẽ một kỹ thuật - dùng Converter, Converter tôi đã hướng dẫn ở bài trước, các bạn có thể xem để biết thêm...
Chúng ta tạo một Enum thể hiện cho các tùy chọn của một groupRadio và class Model
[code language="csharp"]
public enum StylesChecking
{
Is,
NotIs,
Container
}
public class Model: INotifyPropertyChanged
{
StylesChecking _currentOption;
public StylesChecking CurrentOption
{
get { return _currentOption; }
internal set
{
this._currentOption = value;
OnPropertyChanged(); //Xem thêm về Databinding
}
}
#region Event
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
[/code]
Tiếp theo cần implement một class Converter
[code language="csharp"]
public class EnumMatchToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return false;
string checkValue = value.ToString();
string targetValue = parameter.ToString();
// Nếu giá trị param trùng với giá trị hiện tại của thuộc tính trong Model => true
return checkValue.Equals(targetValue,
StringComparison.InvariantCultureIgnoreCase);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return null;
bool useValue = (bool)value;
string targetValue = parameter.ToString();
if (useValue)
{
// Nếu trạng thái hiện tại của radioBtn này là true
// Parse từ string của param sang Enum
return StylesChecking.Parse(targetType, targetValue);
}
return null;
}
}
[/code]
Cuối cùng ta dùng nó như sau
[code language="xml"]
<[RootElementName].Resources>
<[localNamespace]:EnumMatchToBooleanConverter x:Key="enumConverter" />
</[RootElementName].Resources>
<!-- ... ->
<WrapPanel>
<RadioButton GroupName="Options1" Content="Is" HorizontalAlignment="Right"
IsChecked="{Binding Path=CurrentOption, Mode=TwoWay,
Converter={StaticResource enumConverter},
ConverterParameter=Is}"
Height="16" Margin="0,0,4,0"/>
<RadioButton GroupName="Options1" Content="Not is" HorizontalAlignment="Right"
IsChecked="{Binding Path=CurrentOption, Mode=TwoWay,
Converter={StaticResource enumConverter},
ConverterParameter=NotIs}"
Height="16" Margin="0,0,4,0"/>
<RadioButton GroupName="Options1" Margin="0,0,4,0"
IsChecked="{Binding Path=CurrentOption, Mode=TwoWay,
Converter={StaticResource enumConverter},
ConverterParameter=Container}"
Content="Container" HorizontalAlignment="Right" Height="16"/>
</WrapPanel>
[/code]
Lưu ý mỗi RadioButton có một ConverterParameter riêng và nó phải maping với Enum
Chúc các bạn thành công!
Phạm Tuân
Chủ Nhật, 1 tháng 2, 2015
Sau Dependency property, chúng ta sẽ tiếp tục tìm hiểu về Attached property - một loại property không thuộc đối tượng trực tiếp sử dụng nó(khác Dependency property). Dễ hiểu hơn có nghĩa là property này được định nghĩa ở một class/object khác và được dùng như một property của class/object này(na ná cách dùng của Extension methods), bởi thế mới gọi là Attached.
Bạn có thể thấy một số Attached property khi sử dụng các panel: Dock, Grid, Canvas ... Trong ví dụ dưới đây, chúng ta set giá trị DockPanel.Dock cho TextBox là "Top", xong property Dock lại không phải là một property thuộc TextBox - đó là một Attached property được định nghĩa ở DockPanel, attached property này dùng cho tất cả các control đang được chứa bởi DockPanel.
[code language="xml"]
<TextBox DockPanel.Dock=”Top”>Attached property</TextBox>
[/code]
Để tạo một attached property chúng ta phải tuân theo cấu trúc này( hãy liên tưởng đến bài Dependency property trước để có sự so sánh về cách đăng ký một attached property - không khéo là nhầm lẫn đấy)
[code language="csharp"]
public class RotationManager
{
public static propertyType Get[+ propertyName](DependencyObject obj)
{
//TODO: need to implement now
return (propertyType)obj.GetValue([propertyName+]Property);
}
public static void Set[+ propertyName](DependencyObject obj, propertyType value)
{
//TODO: need to implement now
obj.SetValue([propertyName+]Property, value);
}
public static readonly DependencyProperty [propertyName+]Property =
DependencyProperty.RegisterAttached("propertyName",
typeof(propertyType), typeof(ownerType),
new [defaultMetadata](0.5, [propertyChangedCallback]));
private static void propertyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
//TODO: need to implement now
}
}
[/code]
Diễn gải:
- propertyName: Là tên của property bạn muốn tạo ví dụ "Dock"
- propertyType: Là Type mà attached property này nhận
- ownerType: là Type của đối tượng chịu trách nhiệm(Owner) property này, ví dụ DockPanel
- propertyChangedCallback: là hàm sẽ được gọi khi Value của attached property bị thay đổi
- defaultMetadata: là một object thuộc PropertyMetadata - chỉ loại các đối tượng sẽ được thừa hưởng property này
- FrameworkPropertyMetadata: tất cả các element trong XAML
- UIPropertyMetadata: là các UI element/Control trong XAML
Ví dụ trực quan: Tôi có một Path, tôi xoay nó với góc 50 độ và được như hình sau
XAML thế này
[code language="xml"]
<Grid Background="White" Margin="25">
<Path Stretch="Uniform" Fill="#FF08578B" Data="F1 M 28.4583,20.8296C 28.9254,20.4138 29.2514,19.768 29.4364,18.8921C 29.6214,18.0162 29.4554,17.0984 28.9384,16.1389C 29.3694,14.16 29.0234,12.7686 27.9003,11.9646C 26.7772,11.1607 25.4592,10.7729 23.946,10.8015L 23.274,10.8015C 23.614,10.1266 23.882,9.40756 24.0781,8.64438C 24.2741,7.8812 24.3741,7.20871 24.3781,6.62686C 24.3166,4.37555 23.8036,2.70673 22.839,1.62039C 21.8744,0.534058 20.8272,-0.006073 19.6976,0C 17.6181,0.0614014 16.274,1.37863 15.6653,3.95166C 15.6406,4.27734 15.1563,5.48642 14.2124,7.57889C 13.2686,9.67136 11.8711,11.4226 10.02,12.8326L 10.02,11.6045C 9.94067,10.959 9.57507,10.5968 8.92316,10.5181L 1.0975,10.5181C 0.445374,10.5968 0.0795288,10.959 0,11.6045L 0,30.0051C 0.0795288,30.7017 0.445374,31.086 1.0975,31.1581L 8.92316,31.1581C 9.57507,31.086 9.94067,30.7017 10.02,30.0051L 10.02,27.7953C 10.7491,28.3628 12.6754,28.989 15.7988,29.6737C 18.9222,30.3584 21.8093,30.4828 24.46,30.0469C 27.1107,29.6109 28.0914,27.9957 27.4023,25.2011C 28.0253,24.7488 28.4593,24.1283 28.7043,23.3396C 28.9493,22.551 28.8674,21.7143 28.4583,20.8296 Z M 24.2341,20.7815C 25.4911,21.0257 26.0372,21.5341 25.8722,22.3068C 25.7072,23.0794 24.7771,23.5158 23.082,23.6158C 22.812,23.6399 22.68,23.748 22.686,23.9401C 22.692,24.1323 22.872,24.2644 23.226,24.3365C 24.8361,24.7918 25.4081,25.4303 24.9421,26.252C 24.4761,27.0737 23.392,27.5081 21.6899,27.5551C 20.587,27.5541 19.0097,27.388 16.9579,27.0567C 14.9062,26.7254 12.5936,25.9468 10.02,24.7208L 10.02,16.0445C 11.9097,15.114 13.6807,13.4388 15.3332,11.0186C 16.9857,8.59857 17.9977,6.34018 18.3693,4.2435C 18.4157,3.81894 18.5482,3.46432 18.7666,3.17972C 18.985,2.89514 19.2953,2.74728 19.6976,2.73615C 20.1107,2.74524 20.5296,3.09171 20.9543,3.77557C 21.3792,4.45944 21.6083,5.42606 21.6419,6.67551C 21.5589,7.68878 21.227,8.72198 20.646,9.77512C 20.0651,10.8282 19.7331,11.6585 19.6501,12.2657C 19.6491,12.525 19.7697,12.765 20.0118,12.9858C 20.254,13.2066 20.6236,13.324 21.1207,13.3381L 23.946,13.3381C 25.3041,13.4708 26.0562,14.0019 26.2022,14.9312C 26.3482,15.8606 25.8042,16.3916 24.5701,16.5244C 24.3061,16.5867 24.1741,16.7006 24.1741,16.8661C 24.1741,17.0316 24.3061,17.1356 24.5701,17.1781C 25.8192,17.3445 26.4172,17.853 26.3642,18.7038C 26.3112,19.5545 25.6012,20.0389 24.2341,20.157C 23.9221,20.177 23.7661,20.2691 23.7661,20.4332C 23.7661,20.5974 23.9221,20.7135 24.2341,20.7815 Z "
RenderTransformOrigin="0.5,0.5">
<!-- Xoay -->
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="50"/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Grid>
[/code]
Chú ý rằng tôi xoay Path trên nhờ đoạn XAML sau
[code language="xml"]
<Path Stretch="Uniform" Fill="#FF08578B" Data="............."
RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="50"/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path></pre>
<pre>[/code]
Nhưng tôi thấy nó quá dài - đến vài thẻ XAML cho một giá trị Angle="50", nếu tôi dùng phép xoay này nhiều lần trong một file XAML hoặc nhiều file XAML khác thì thật rối mắt, vậy nên tôi tạo ra một attached property thay thế đoạn XAML kia như sau
[code language="csharp"]
public class RotationManager
{
/// <summary>
/// Get giá trị của attached property
/// </summary>
/// <param name="obj">Đối tượng Dependency</param>
/// <returns>Giá trị của attached property</returns>
public static double GetAngle(DependencyObject obj)
{
return (double)obj.GetValue(AngleProperty);
}
/// <summary>
/// Set giá trị góc cho đối tượng
/// </summary>
/// <param name="obj">Đối tượng Dependency</param>
/// <param name="value">Giá trị do user input</param>
public static void SetAngle(DependencyObject obj, double value)
{
obj.SetValue(AngleProperty, value);
throw new NoNullAllowedException();
}
public static readonly DependencyProperty AngleProperty =
DependencyProperty.RegisterAttached("Angle",
typeof(double), typeof(RotationManager),
new UIPropertyMetadata(0.5, OnAngleChanged));
/// <summary>
/// CallBack khi value của property thay đổi
/// </summary>
/// <param name="obj">Đối tượng Dependency</param>
/// <param name="e"></param>
private static void OnAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as UIElement;
if (element != null)
{
element.RenderTransformOrigin = new Point(.5, .5);
element.RenderTransform = new RotateTransform((double)e.NewValue);
}
}
}
[/code]
Và dùng đơn giản thế này, tôi tiến hành xoay góc 180 độ
[code language="xml"]
<Grid Background="White" Margin="25">
<Path Stretch="Uniform" Fill="#FF08578B" Data="Như củ" RenderTransformOrigin="0.5,0.5"
local:RotationManager.Angle="180"/>
</Grid>
[/code]
Hãy so sánh với đoạn code trước
[code language="xml"]
<!-- khi không dùng attached property -->
<Path Stretch="Uniform" Fill="#FF08578B" Data="............."
RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="50"/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<!-- khi dùng attached property thì code chỉ thế này -->
<Path Stretch="Uniform" Fill="#FF08578B" Data="............."
RenderTransformOrigin="0.5,0.5"
local:RotationManager.Angle="180"/>
[/code]
Kết quả
Chúc mọi người thành công!
Phạm Tuân
Thứ Bảy, 31 tháng 1, 2015
Khi các bạn làm một dự án khá lớn, nhu cầu tạo ra các Custom Control là không tránh khỏi, trong WinForm hay WPF đều cho phép chúng ta tạo ra các User-Control(userCtrl) với mục đích trên; Nói về khía cạnh WPF, để tạo ra một user-control có tính thuận tiện và linh hoạt cao chúng ta cần áp dụng nhiều kỹ thuật trong thiết kế cũng như lập trình - hôm nay chúng ta sẽ nói về một điều trong nhiều điều phải nắm nếu có ý định tạo một Control mang phong cách cá nhân.
Trong ví dụ sau đây tôi có một user-Control, userCtrl này chứa một TextBox, một Button và một Lable.
Trong XAML tôi thiết kế như sau
[code language="xml"]
<UserControl x:Name="userControl" x:Class="Dependency_property.userControls.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="100" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Background="#FF3399FF">
<Label Content="Header" VerticalContentAlignment="Center" Padding="5,1" Foreground="White"/>
</Grid>
<TextBox Grid.Column="1" VerticalContentAlignment="Center" Padding="5,1"/>
<Button x:Name="btnAction"Grid.Column="2"/>
</Grid>
</UserControl>
[/code]
Vấn đề phát sinh khi tôi dùng userCtrl này, tôi không thể nào thay đổi được nội dung của Button và cũng như không thể Get giá trị từ TextBox bởi vì các control này đã được che phủ bởi userCtrl nên tôi không thể nào truy cập được đến các thuộc tính của các control trong nó; giải quyết vấn đề này bằng Code-behind thì khá đơn giản nhưng chúng ta lại đang làm việc với WPF - XAML và vấn đề trực quan trong design là quan trọng.
[code language="xml"]
<StackPanel>
<!-- Làm sao để set Content cho Button bên trong userCtrl đây -->
<!-- Làm gì thấy được Button mà set trực tiếp -->
<local:MyuserControl x:Name="txtAdv"/>
</StackPanel>
[/code]
Cách giải quyết tôi đề cập ở trên cho code-behind là chúng ta tạo một số property của userCtrl làm cầu nối để set hoặc get value của các control bên trong - giống như cách nhìn nhận trong bài viết Create intance ; xong cách làm này có một số nhược điểm và Microsoft không khuyến cáo dùng(Cứ thử làm với các project lớn đi rồi sẽ gặp một vài vấn đề... hihi); Vì thế chúng ta dùng Dependency Property, ưu điểm là Dependency Property sẽ hỗ trợ nhiều khía cạnh hơn trong WPF(Style, Template, kế thừa property, hình ảnh động, 3D...) Cách dùng Dependency Property như sau:
1> Đăng ký Dependency Property
Trong code behind của userCtrl chúng ta tạo một Dependency Property
[code language="csharp"]
// Cho button
public static readonly DependencyProperty
ButtonContentproperty = DependencyProperty.Register(
"ButtonContent", typeof (object), typeof (MyuserControl),
new UIPropertyMetadata("Action"));
// Cho textBox
public static readonly DependencyProperty
TextBoxTextproperty = DependencyProperty.Register(
"TextBoxText", typeof (string), typeof (MyuserControl),
new UIPropertyMetadata("No text", OnValueChanged));
// Sự khác biệt khi textBox có thêm method "OnValueChanged"
// Vì tôi muốn làm một vài điều khi nội dung textBox bị changed
[/code]
Lưu ý:
- "ButtonContentproperty" là tên của DependencyProperty, property này sẽ được đăng ký với Type là object vì Content của Button là một object, Type của userCtrl sẽ sử dụng được nó là MyuserControl, và giá trị mặc định là một chuổi "Action".
- ButtonContentproperty này sẽ không thật sự dùng để chúng ta làm việc với XAML đâu nhé, trong XAML chúng ta truy xuất đến DependencyProperty với tên "ButtonContent" - lưu ý tên này(*)
2> Tạo một property thông thường
Tiếp theo tạo lần lược các property với kiểu trùng kiểu của DependencyProperty tương ứng, tên là tên tương ứng (ví dụ "ButtonContent" trùng với (*))
[code language="csharp"]
// Cho button
public object ButtonContent
{
// Property này sẽ truy xuất đến DependencyProperty trên kia
get { return GetValue(ButtonContentproperty); }
set { SetValue(ButtonContentproperty, value); }
}
// Cho textBox
public string TextBoxText
{
// Property này sẽ truy xuất đến DependencyProperty trên kia
get { return (string) GetValue(TextBoxTextproperty); }
set { SetValue(TextBoxTextproperty, value); }
}
[/code]
3> Làm việc với Event
Tới đây chúng ta đã xong phần DependencyProperty, để hoàn chỉnh userCtrl này chúng ta cần xử lý việc Click button, đơn giản là tại code-behind của userCtrl ta public một event "ButtonClick" kiểu RoutedEventHandler để bên ngoài có thể tiếp cận; tại XAML của userCtrl, sự kiện Click của Button được chỉ định đến một Method "ActionExecuting" trong code-behind của userCtrl - tại đây chúng ta kiểm tra và thực thi event "ButtonClick"
[code language="csharp"]
// Nơi dùng userCtrl sẽ đăng ký event này
public event RoutedEventHandler ButtonClick;
// Khi Button.Click sảy ra, hàm dưới đây sẽ được gọi để thực thi các đăng ký event
private void ActionExecuting(object sender, RoutedEventArgs e)
{
if (ButtonClick != null)
{
ButtonClick.Invoke(this, new RoutedEventArgs());
}
}
[/code]
XAML của userCtrl
[code language="xml"]
<!-- nội dung khác -->
<Button x:Name="btnAction" Grid.Column="2" Margin="2,0,0,0"
Content="{Binding ButtonContent, ElementName=userControl,
UpdateSourceTrigger=PropertyChanged}"
Click="ActionExecuting"/>
<!-- nội dung khác -->
[/code]
Lưu ý: cách xử lý này vẫn cùi mía, cách rồ hơn đề cập sau
4> Cách dùng
Hãy xem về Binding nếu bạn chưa biết nhé, sau đây tôi binding Content/Text của các control đến property của userCtrl(là property chứ không phải DependencyProperty nha)
[code language="xml"]
<UserControl x:Name="userControl" x:Class="Dependency_property.userControls.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="100" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Background="#FF3399FF">
<Label Content="Header" VerticalContentAlignment="Center" Padding="5,1" Foreground="White"/>
</Grid>
<TextBox Text="{Binding TextBoxText, ElementName=userControl, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1"
VerticalContentAlignment="Center" Padding="5,1"/>
<Button x:Name="btnAction"
Content="{Binding ButtonContent, ElementName=userControl, UpdateSourceTrigger=PropertyChanged}"
Click="ActionExecuting" Grid.Column="2"/>
</Grid>
</UserControl>
<!-- trong nội dung binding, ElementName=userControl là chỉ userCtrl cha -->
[/code]
Gọi dùng user-Control ở MainWindow
[code language="xml"]
<Window x:Class="Dependency_property.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:Dependency_property.userControls"
Title="MainWindow" Height="200" Width="525">
<Grid>
<local:MyuserControl x:Name="txtAdv" VerticalAlignment="Center" Margin="10,0" Height="30"
ButtonClick="TxtAdv_OnButtonClick"
TextBoxText="Hello world! this is user-control"
ButtonContent="Show" />
</Grid>
</Window>
[/code]
Kết quả
Chúc các bạn thành công! source code
Phạm Tuân
"Làm sao để tạo một thể hiện của một class bằng việc sử dụng code XAML?", việc tạo một Instance trong code Behind là quá đơn giản (var intance = new ABC{ PrA = "property", PrB = 10};) xong để khởi tạo một Intance và gán giá trị cho các property cho nó thông qua code XAML thế nào chúng ta sẽ cùng xem ví dụ sau đâu.
I> Phần I: Tạo một Intance từ Internal class
Tôi tạo một Class như sau trong chính project của mình
[code language="csharp"]
public class Book
{
public string Name { get; set; }
public string Author { get; set; }
public decimal Price { get; set; }
public int YearPublished { get; set; }
}
[/code]
Tôi gọi là internal class vì class này cùng namespace với class MainWindow và chúng có quan hệ nội bộ
Để tạo intance của Class Book bằng code XAML tôi làm như sau
[code language="xml"]
<Window x:Class="Creating_instances_in_XAML.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Creating_instances_in_XAML"
Title="MainWindow" Height="200" Width="400">
<StackPanel>
<Button>
<local:Book Name="Study WPF"
Author="Phạm Tuân" Price="40"
YearPublished="2015" />
</Button>
<ContentControl>
<local:Book Name="Study WPF QuickStart"
Author="Phạm Tuân" Price="40"
YearPublished="2015" />
</ContentControl>
</StackPanel>
</Window>
[/code]
Hãy chú ý rằng:
- "local" là một từ khóa đại diện cho một namespace chứa class mà bạn muốn tạo intance
- <local:Book /> chính là class Book sẽ được tạo intance, các property của class Book được gọi tên và gán giá trị bên trong thẻ này.
- Một intance không kế thừa từ Controls sẽ phải được bao bọc bởi một control intance nào đó - tức thẻ <local:Book /> phải nằm trong thẻ của một số control chứ không được viết trực tiếp trong một thẻ panel
Kết quả hiển thị cho ta điểu này
Các bạn sẽ tự hỏi cái chúng ta nhận được trên GUI liệu có mục đích gì khi phần nội dung không có ý nhĩa, vâng! chúng là vô dụng nếu chỉ hiển có thế. Quay lại class Book vừa tạo và thay đổi như sau
[code language="csharp"]
public class Book
{
public string Name { get; set; }
public string Author { get; set; }
public decimal Price { get; set; }
public int YearPublished { get; set; }
public override string ToString()
{
return string.Format("{0} by {1}, Published {2}", Name,
Author, YearPublished);
}
}
[/code]
ToString() sẽ được tự động gọi để in dữ liệu ra GUI. Kết quả ta nhận được là
Đó là điều chúng ta cần trong một số trường hợp với Binding.
II> Phần II: Tạo một Intance từ External class
Trước tiên tôi tạo mới một project là Class Library, trong project này tôi tạo một class là Book như ở phần I
[code language="csharp"]
namespace ClassLibrary
{
public class Book
{
public string Name { get; set; }
public string Author { get; set; }
public decimal Price { get; set; }
public int YearPublished { get; set; }
public override string ToString()
{
return string.Format("{0} by {1}, Published {2}", Name,
Author, YearPublished);
}
}
}
[/code]
Quay lại project WPF tôi add reference đến project library trên, code XAML tôi dùng như dưới
[code language="xml"]
<Window x:Class="Creating_instances_in_XAML.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Creating_instances_in_XAML"
xmlns:classLibrary="clr-namespace:ClassLibrary;assembly=ClassLibrary"
Title="MainWindow" Height="200" Width="400">
<StackPanel>
<Button>
<local:Book Name="Study WPF"
Author="Phạm Tuân" Price="40"
YearPublished="2015" />
</Button>
<Button>
<classLibrary:Book Name="Study WPF QuickStart"
Author="Phạm Tuân" Price="80"
YearPublished="2019" />
</Button>
</StackPanel>
</Window>
[/code]
Chú ý hai từ khóa local và classLibrary, bạn đã thấy sự khác biệt chưa, hãy lưu ý cách khai báo này cho internal và external class
[code language="csharp"]
xmlns:local="clr-namespace:Creating_instances_in_XAML"
xmlns:classLibrary1="clr-namespace:ClassLibrary;assembly=ClassLibrary"
[/code]
Ngoài ra chúng ta còn cách khác để khai báo trước khi dùng thư viện ngoài như sau - cách này chuyên nghiệp và chính thống hơn. Bạn có để ý thấy rằng để refer đến một library của Microsoft trong XAML người ta chỉ dùng Web Address không, để dùng được như thế ta quay lại Class Book ở project Library và thêm một Attribute vào namspace như thế này
[code language="csharp"]
using System.Windows.Markup;
[assembly: XmlnsDefinition("http://mylibrary.com", "ClassLibrary")]
namespace ClassLibrary
{
public class Book
{
public string Name { get; set; }
public string Author { get; set; }
public decimal Price { get; set; }
public int YearPublished { get; set; }
public override string ToString()
{
return string.Format("{0} by {1}, Published {2}", Name,
Author, YearPublished);
}
}
}
[/code]
Ở XAML chúng ta thay thế cái cần thay thế
[code language="xml"]
<!-- Thay dòng sau -->
xmlns:classLibrary="clr-namespace:ClassLibrary;assembly=ClassLibrary"
<!-- bằng dòng dưới này-->
xmlns:classLibrary="http://mylibrary.com"
[/code]
Chúng ta có cùng một kết quả như phần I. Nếu chúng ta có các sub-namspace thì làm như sau
[code language="csharp"]
using System.Windows.Markup;
// Address sẽ thêm như sau
[assembly: XmlnsDefinition("http://mylibrary.com/OrtherBook", "ClassLibrary.OrtherBook")]
namespace ClassLibrary.OrtherBook
{
public class StoryBook
{
public string Name { get; set; }
public string Author { get; set; }
public decimal Price { get; set; }
public int YearPublished { get; set; }
public override string ToString()
{
return string.Format("{0} by {1}, Published {2}", Name,
Author, YearPublished);
}
}
}
[/code]
Code XAML dùng khác hơn một xíu
[code language="xml"]
<Window x:Class="Creating_instances_in_XAML.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Creating_instances_in_XAML"
<!-- Lưu ý sự thay đổi tại dòng dưới-->
xmlns:ortherBook="http://mylibrary.com/OrtherBook"
Title="MainWindow" Height="200" Width="400">
<StackPanel>
<Button>
<ortherBook:StoryBook Name="Study WPF QuickStart"
Author="Phạm Tuân" Price="80"
YearPublished="2019" />
</Button>
</StackPanel>
</Window>
[/code]
Chúc các bạn thành công
Phạm Tuân
Thứ Ba, 20 tháng 1, 2015
Như các bạn đã biết, WPF có khá nhiều điểm mạnh và mới so với Winform trước kia, trong đó có Binding, 3D, XAML... và hôm nay chúng ta tìm hiểu về Storyboard - Nó được sử dụng để tạo ra các kịch bản hoạt họa/hoạt hình cho giao diện WPF. Để một đối tượng hình học(Sharp) có thể di chuyển trong một Panel thường chúng ta phải viết khá nhiều dòng code-behind nhưng với WPF chúng ta không nhất thiết phải làm như thế; sẽ có công cụ cho chúng ta tạo kịch bản.
I> Storyboard là gì
Trước tiên để hình dung được công dụng của Storyboard tôi sẽ có ví dụ sau đây. Tôi sẽ tạo một cửa sổ có chứa một hình Chữ nhật(với Radius khá lớn để biến thành hình tròn - chỉ để đẹp thôi), hình chữ nhật này sẽ thay đổi kích thước và màu nền khi sự kiện Rectangle.MouseEnter sảy ra - Chú ý rằng Storyboard hỗ trợ cho tất cả các loại Trigger trong code XAML, tham khảo thêm bản sau:
| Storyboard is begun using… | Per-instance | Style | Control template | Data template | Example |
|---|---|---|---|---|---|
| BeginStoryboard and an EventTrigger | Yes | Yes | Yes | Yes | How to |
| BeginStoryboard and a property Trigger | No | Yes | Yes | Yes | How to |
| BeginStoryboard and a DataTrigger | No | Yes | Yes | Yes | How to |
| Begin method | Yes | No | No | No | How to |
Có thể thực hiện ý tưởng trên bằng hai cách tương đương:
=> Code XAML
[code language="xml"]
<Window x:Class="Storyboard.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Rectangle Name="MyRectangle"
Width="200"
Height="200" RadiusY="100" RadiusX="100">
<Rectangle.Fill>
<SolidColorBrush x:Name="MySolidColorBrush" Color="Blue" />
</Rectangle.Fill>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<BeginStoryboard>
<Storyboard AutoReverse="True">
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width"
From="200" To="300" Duration="0:0:1" />
<ColorAnimation
Storyboard.TargetName="MySolidColorBrush"
Storyboard.TargetProperty="Color"
From="Blue" To="Red" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Grid>
</Window>
[/code]
=> Code Behind
[code language="csharp"]
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Storyboarddf = System.Windows.Media.Animation;
using System.Windows.Data;
using System.Windows.Shapes;
using System.Windows.Input;
using System.Windows.Media.Animation;
namespace Storyboard
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
StoryboardsExample();
}
public void StoryboardsExample()
{
StackPanel myStackPanel = new StackPanel();
myStackPanel.Margin = new Thickness(20);
Rectangle myRectangle = new Rectangle();
myRectangle.Name = "MyRectangle";
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(myRectangle.Name, myRectangle);
myRectangle.Width = 200;
myRectangle.Height = 200;
myRectangle.RadiusX = 100;
myRectangle.RadiusY = 100;
SolidColorBrush mySolidColorBrush = new SolidColorBrush(Colors.Blue);
this.RegisterName("MySolidColorBrush", mySolidColorBrush);
myRectangle.Fill = mySolidColorBrush;
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 200;
myDoubleAnimation.To = 300;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboarddf.Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboarddf.Storyboard.SetTargetProperty(myDoubleAnimation,
new PropertyPath(Rectangle.WidthProperty));
ColorAnimation myColorAnimation = new ColorAnimation();
myColorAnimation.From = Colors.Blue;
myColorAnimation.To = Colors.Red;
myColorAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboarddf.Storyboard.SetTargetName(myColorAnimation, "MySolidColorBrush");
Storyboarddf.Storyboard.SetTargetProperty(myColorAnimation,
new PropertyPath(SolidColorBrush.ColorProperty));
Storyboarddf.Storyboard myStoryboard = new Storyboarddf.Storyboard();
myStoryboard.AutoReverse = true;
myStoryboard.Children.Add(myDoubleAnimation);
myStoryboard.Children.Add(myColorAnimation);
myRectangle.MouseEnter += delegate(object sender, MouseEventArgs e)
{
Begin(this);
};
myStackPanel.Children.Add(myRectangle);
this.Content = myStackPanel;
}
}
}
[/code]
Storyboard được sử dụng nhiều trong việc tạo ra các giao diện thay đổi trạng thái theo cách người dùng tương tác cũng như thực hiện các chuyển động với kịch bản không quá phức tạp và không cần nhiều sự linh hoạt; Storyboard có hai property ta cần quan tâm nhất đó là AutoReverse=["True"] và RepeatBehavior=["Forever"]
- AutoReverse: Sau khi thực hiện xong kịch bản, đối tượng sẽ trở lại trạng thái cũng như vị trí ban đầu - thực hiện ngược kịch bản trước
- RepeatBehavior: Sau khi thực hiện xong kịch bản, đối tượng tiếp tục thực hiện lại nó cho đến khi được gọi Pause hoặc Stop trong code-Behind
II> Tạo kịch bản Storyboard với công cụ
Ở một bài trước tôi có giới thiệu về về một công cụ giúp thiết kế giao diện cho WPF(Expression Studio), hôm nay chúng ta sẽ sử dụng nó. Trước tiên chúng ta cần tải về và cài đặt công cụ này tại đây, có nhiều tùy chọn trong bộ công cụ nhưng chúng ta chỉ cần Blend là đủ/cũng có thể cài full nếu cần thiết.
[code language="csharp"]
TJ2R3-WHW22-B848T-B78YJ-HHJWJ
6WDDQ-K7D4F-GQGF4-2VYBJ-8K6MB
6WDDQ-K7D4F-GQGF4-2VYBJ-8K6MB
6WDDQ-K7D4F-GQGF4-2VYBJ-8K6MB
YV688-DW39R-JPKH2-6DG4R-HM9JD
[/code]
Sau khi cài đặt xong chúng ta khởi chạy ứng dụng và tạo mới một Project WPF- trình tự như hình sau
[caption id="" align="aligncenter" width="350"]
[caption id="" align="aligncenter" width="550"]
[caption id="" align="aligncenter" width="783"]
[caption id="" align="aligncenter" width="733"]
[caption id="" align="aligncenter" width="314"]
[caption id="" align="aligncenter" width="440"]
Tại giao diện biên tập Storyboard chúng ta có biểu tượng Recording phía trên-phải sáng lên và thanh TimeLine, thanh Timeline lưu lại trạng thái của từng đối tượng cho các móc thời gian trên thước đo. Khi một thuộc tính nào đó của đối tượng thay đổi tại một cột thời gian kế tiếp thì hệ thống sẽ tự in dấu và lưu giữ bước biên tập hiện tại - thể hiện bằng dấu chấm trắng trên cột Time. Công việc của chúng ta là kéo cột Time màu vàng qua lần lược các cột timer, sau đó thực hiện thay đổi thuộc tính cho đối tượng đích và lập lại đến khi hết kịch bản.
[caption id="" align="aligncenter" width="544"]
[caption id="" align="aligncenter" width="689"]
Để khi kết thúc kịch bản, các đối tượng trở lại vị trí cũ ta Set property AutoReverse="True"
[caption id="" align="aligncenter" width="449"]
Vậy là chúng ta có một kịch bản hoàn chỉnh như sau: Quả bóng tròn lăng sang phải khi CheckBox "Run" được Checked, khi quả bóng lăng đụng khối vuông, khối vuông biến thành tròn và bị văng đi một đoạn. Sau đó ta thấy kịch bản bị tua ngược lại vì thuộc tính AutoReverse="True".
Để Start một Storyboard đã được định nghĩa trong [Resource] ta có hai cách đó là [1]Start trong Triger hoặc [2]Start trong Code-behind, Cách một được dùng như tôi hướng dẫn ở trên, cách hai ta làm như sau
Định ra một Storyboard
[code language="xml"]
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Storyboard x:Key="PlayAnimation" Storyboard.TargetProperty="(Canvas.Left)">
<DoubleAnimation From="0" To="100" Duration="0:0:1"/>
</Storyboard>
</Window.Resources>
<Canvas>
<Button x:Name="btn">Test</Button>
</Canvas>
</Window>
[/code]
Tìm và khởi động storyboard trên như sau đây
[code language="csharp"]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Storyboard sb = this.FindResource("PlayAnimation") as Storyboard;
//Không cần dòng dưới nếu trong XAML đã thể hiện điều này - Target
Storyboard.SetTarget(sb, this.btn);
// End Find
//Start
sb.Begin();
}
}
[/code]
Chúc các bạn vui vẻ!
Phạm Tuân
Thứ Bảy, 10 tháng 1, 2015
Một khía cạnh hết sức quan trọng trong WPF đó là Trigger - Trigger giúp nắm bắt được mọi thay đổi trong một hệ thống GUI của WPF. Nhờ có Trigger một chương trình WPF sẽ trở nên gần gủi và thân thiện với người dùng hơn, trigger sẽ được kích hoạt cho mọi sự thay đổi trong trạng thái của từng Control/Element qua đó chúng ta sẽ chọn cách ứng xử phù hợp. Trigger thường được dùng trong Style và Template.
Trigger trong WPF có 3 loại:
- PropertyTrigger
- DataTrigger
- EventTrigger
Chúng ta sẽ tìm hiểu từng loại Trigger
I> Trigger
Là một dạng Trigger được kích hoạt khi một properry bất kỳ của Element bị thay đổi, ví dụ như trạng thái, màu sắc, kích thước. Chúng ta sẽ thử làm một ví dụ về PropertyTrigger.
Tôi sẽ tạo một TextBox, mặc định TextBox này sẽ có chữ màu Xanh; Tôi muốn màu chữ sẽ chuyển sang Xanh khi thuộc tính IsEnabled = false; Thuộc tính IsEnabled được binding đến một CheckBox.
Tôi áp dụng một Style như sau để có một TextBox mặc định, chú ý với PropertyTrigger chúng ta chỉ dùng thẻ Trigger.

[code language="xml"]
<StackPanel x:Name="LayoutRoot" Margin="0,50,0,0">
<TextBox VerticalAlignment="Center" HorizontalAlignment="Left" FontWeight="Bold"
IsEnabled="{Binding IsChecked, ElementName=checkBox, IsAsync=True}"
MinWidth="100" Margin="0" Height="24" Text="ABCD XYZ" VerticalContentAlignment="Center">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="Green" />
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
[/code]
Và đây là kết quả khi tôi dùng Trigger

[code language="xml"]
<WrapPanel VerticalAlignment="Top" HorizontalAlignment="Center">
<TextBox VerticalAlignment="Center" HorizontalAlignment="Left" FontWeight="Bold"
IsEnabled="{Binding IsChecked, ElementName=checkBox, IsAsync=True}"
MinWidth="100" Margin="0" Height="24" Text="ABCD XYZ" VerticalContentAlignment="Center">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="Green" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox x:Name="checkBox" IsChecked="True" Content="IsEnabled for TextBox" Margin="20,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</WrapPanel>
[/code]
II> Data triggers
Khác với PropertyTrigger ở chỗ, DataTrigger sẽ kiểm soát sự thay đổi về mặt dữ liệu của các Control/Element - tức phạm vi không chỉ giới hạn ở các property mà là tất cả các dạng Data có thể binding.
Ví dụ dưới đây tôi có 2 TextBox(trên và dưới), tôi áp dụng DataTrigger cho textBox trên, TextBox này sẽ có màu chữ mặc định là Xanh, nhưng khi tôi ghi chữ "Checked" vào ô TextBox dưới thì ở TextBox trên sẽ có màu chữ là Xanh.

[code language="xml"]
<StackPanel VerticalAlignment="Top" Margin="10,0">
<TextBox VerticalAlignment="Center"
MinWidth="100" Margin="0" Text="Tiềm Hiểu Về Trigger Trong WPF" VerticalContentAlignment="Center" Height="20">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="Green" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=textBox}" Value="Checked">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="textBox" VerticalAlignment="Center"
MinWidth="100" Margin="0,10,0,0" Text="Checked" VerticalContentAlignment="Center" Height="20"/>
</StackPanel>
[/code]
III> Event triggers
EventTriggers dùng khi phải làm việc với các sự kiện của chuột, bàn phím... Tôi thường thấy mọi người dùng loại Trigger này kèm theo Storyboard(Sẽ nói về vấn đề này ở bài sau) nhằm thực thi một chuyển động, hiệu ứng màu sắc...
Sau đây tôi sẽ dùng EventTriggers để xử lý sự kiện MouseEnter, khi người dùng di chuyển chuột vào vùng TextBox, lập tức TextBox này sẽ Trigger sự kiện chuột và thực thi một hành động đó là phóng to chữ.

[code language="xml"]
<TextBox VerticalAlignment="Center"
MinWidth="100" Margin="0" Text="Tiềm Hiểu Về Trigger Trong WPF" VerticalContentAlignment="Center" Padding="0,0,0,1">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="Green" />
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<DoubleAnimation Duration="0:0:0.800" Storyboard.TargetProperty="FontSize" To="16" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
[/code]
Nếu áp dụng thành thục các Trigger trên vào Styles và Templates chắc chắn chúng ta sẽ có một ứng dụng vô cùng tiện lợi và đẹp mặt người dùng.
Chúc các bạn thành công!
Phạm Tuân
Chủ Nhật, 4 tháng 1, 2015
Windows Presentation Foundation(WPF) cho phép các nhà phát triển có thể thay đổi hoàn toàn cái nhìn tổng quan và cách ứng xử của các Control. Điều này được thực hiện bằng cách sử dụng Control-Style và Control-Templates. Điều đó có nghĩa là bạn có một Button nhưng nó không chỉ đơn điệu là một hình chữ nhật mà có thể là một hình ảnh náo đó mà bạn muốn. Ở nội dung này tôi sẽ hướng dẫn các bạn tạo một Control-Templates cho TextBox.
Trước hết chúng ta cần phân biệt khái niệm Style và Tamplates
- Style: là cách thể hiện của một đối tượng về màu sắc, độ trong suốt.. và các thuộc tính ấy được phủ lên đối tượng - chúng có thể được ghi đè lên các thuộc tính mặc định nếu cần thiết, các Style có thể được áp dụng cho tất cả các Element cùng loại.
- Template: là cách thể hiện nội dung mang tính "cơ bắp" hơn, nó như một bộ khung được đặc lên trên bộ khung mặc định và không thay thế chúng, các Template chỉ được áp dụng cho các Control.
Một ví dụ cho sự khác biệt giữa Style và Tamplates: Style như một bộ đồ, nếu bộ đồ ấy dành cho con người dĩ nhiên phải có tay áo, cổ áo, ống quần... tất cả những thứ đó một người bình thường đều phải có, bộ đồ ấy được khoác lên người và chúng ta sẽ không thể thấy được phần tự nhiên trước đó, trong khi đó Template có thể là một bộ cánh, một chiếc giáp chiến đồ sộ hoặc cũng có thể là một cánh tay thứ ba... Và bộ cánh sẽ mang nhiều tính hành xử hơn một bộ đồ vì thế Styles áp dụng cho Element và Template chỉ áp dụng cho Control.
Các Style và Tamplates có thể được định nghĩa trong một thẻ con của đối tượng cần áp dụng(chỉ có tác dụng trong đối tượng đó)
- <TextBox.Style>[my style]</TextBox.Style>
- <TextBox.Template>[my template]</TextBox.Template>
hoặc được định nghĩa trong cặp thẻ Resources của thẻ root(phạm vi sử dụng trong thẻ root)
- <Window.Resources>[my style][my template]</Window.Resources>
- <Control.Resources>[my style][my template]</Control.Resources>
hay mở rộng phạm vi sử dụng lớn tối đa bằng cách định nghĩa chúng trong một file Resource riêng và đăng ký resource trong file App.xaml
Hôm nay tôi sẽ hướng dẫn các bạn cách tạo Style và Template cho TextBox, các bạn có thể xem các Template và Styles tôi đã làm sẵn dưới đậy.
Trước tiên chúng ta sẽ Add một file Resource bằng cách Add->New item và chọn Resource(WPF)
[code language="xml"]
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d">
<!-- Các resource được được định nghĩa dưới đây-->
<!-- Các resource được được định nghĩa bên trên-->
</ResourceDictionary>
[/code]
Quay lại file App.xaml, chúng ta hoàn thiện việc đăng ký resource như sau
[code language="xml"]
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication1.App"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- Resources scoped at the Application level should be defined here. -->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Các tập tin resource được đăng ký bên dưới-->
<ResourceDictionary Source="ReDicTemplate.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
[/code]
I> Cách tạo một Style
Tôi thêm một thẻ <Style/> với nội dung hoàn thiện bên dưới, trong đó các property "Key, TargetType, BasedOn" nhất thiết phải có.
Mục đích của Style này là các TextBox áp dụng nó đều sẽ có nền màu xanh và chữ trắng, Padding = 5 và BorderThickness = 1, lượi ích là tôi sẽ tiết kiệm khá nhiều thời gian cho việc quy định các thuộc tính trên cho 1000 TextBox giống nhau.
[code language="xml"]
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d">
<!-- Các resource được được định nghĩa dưới đây-->
<!-- Một Style phải có x:Key-->
<Style x:Key="TextBoxStyle1" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Green"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- Có thể định nghĩa một Template như thế này-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Các resource được được định nghĩa bên trên-->
</ResourceDictionary>
[/code]
Key --> là mã nhận dạn cho một đối tượng trong ngôn ngữ XAML, được dùng khi muốn áp dụng style
BasedOn --> Chỉ ra một style parent bạn muốn kế thừa, tôi không kế thừa từ style nào cả
TargetType --> Chỉ ra một kiểu đối tượng có thể áp dụng style này, ở đây tôi dùng cho TextBox
Quay về Control chúng ta muốn áp dụng Style và set propery Style của control đó với giá trị là Key của Style trên.
[code language="xml"]
<TextBox Margin="10,10,10,0" Tag="My Name"
Text="Pham Tuan" VerticalAlignment="Top"
Style="{DynamicResource TextBoxStyle1}"/>
[/code]
Kết quả
Như vậy là xong phần Style, để có một style thật sự đẹp mắt chúng ta cần phải sử dụng nhiều kỹ thuật khác nhau Binding, Trigger, Storybroad ... tôi sẽ hướng dẫn ở một bài khác.
II> Cách tạo một Template
Template có hai dạng: Control Template và Data Template
- Control Template: Là cách thức hiển thị của một Control, tức vẽ bề ngoài của chính nó
- Data Template: Là cách hiển thị của phần dữ liệu mà nó chứa đựng, tức vẽ bề ngoài của các Item khi nó hiển thị trong lòng mình(Thường dùng với các control như ListBox, GridView, ComboBox...)
A> ControlTemplate
Tôi thêm một thẻ <ControlTemplate/> với nội dung hoàn thiện bên dưới, trong đó các property "Key, TargetType" nhất thiết phải có.
Mục đính tôi tạo một Template vì tôi muốn có một TextBox với phần Title hiển thị phía trước và phần nhập nội dung lền kề bên phải, phần cuối cùng bên phải TextBox có một Icon làm cảnh, màu nền phần Title trùng màu viền của TextBox(Nên tôi dùng TemplateBinding BorderBrush), nội dung Title được Binding đến thuộc tính Tag của TextBox(Nên tôi dùng TemplateBinding Tag)
[code language="xml"]
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d">
<!-- Các resource được được định nghĩa dưới đây-->
<!-- Phần Style ở trên sẽ ở đây-->
<!-- Một ControlTemplate phải có x:Key-->
<ControlTemplate x:Key="TextBoxBaseTemplate" TargetType="{x:Type TextBoxBase}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" MinWidth="100" Content="{TemplateBinding Tag}" Padding="4,1,4,2" VerticalContentAlignment="Center" Background="{TemplateBinding BorderBrush}" Foreground="White"/>
<ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Grid.ColumnSpan="1" Grid.Column="1" VerticalContentAlignment="Center" Padding="0,0,0,2"/>
<Label FontFamily="Webdings" Content="." VerticalContentAlignment="Center" Padding="5,0" Foreground="White" Margin="0" Grid.Column="3" Background="{TemplateBinding BorderBrush}"/>
</Grid>
</Border>
</ControlTemplate>
<!-- Các resource được được định nghĩa bên trên-->
</ResourceDictionary>
[/code]
Border --> kể từ thẻ này đến các thẻ con là phần chúng ta thiết kế
ScrollViewer --> là thẻ mặc định của TextBox chịu trách nhiệm hiển thị và nhận key nhập từ bản phím, tôi giữ nguyên như mặc định.
Chú ý --> Chúng ta sẽ bắc gặp một định dạng như sau Content="{TemplateBinding Tag}" ý nghĩa như sau: TemplateBinding là một kiểu DataBinding nhưng Binding đến các thuộc tính của một Control đã được chỉ ra tại thuộc tính TargetType.
Cũng như Style, chúng ta áp dụng template cho một control bằng cách gọi key của nó
[code language="xml"]
<TextBox Margin="10,10,10,0" TextWrapping="Wrap" Tag="My Name"
Text="Pham Tuan" VerticalAlignment="Top" BorderBrush="#FF007ACC"
Template="{DynamicResource TextBoxBaseTemplate}" Padding="0,2"/>
[/code]
Kết quả
B> DataTemplate
Tôi xin nhắc lại rằng, DataTemplate dùng để thể hiện cho một Item trong ItemSource của các control có khả năng chứa các list item như ComboBox, ListBox, GridView...
Ở ví dụ này tôi có một class Per chứa thông tin một người gồm Name, YearOld, Address, Title, nếu tôi dùng một ListBox đơn thuần - tôi sẽ hiển thị được duy nhất một nội dung tương ứng một property trong các property của đối tượng Per cho mỗi item của ListBox.
[code language="csharp"]
internal class Per
{
public string Title { set; get; }
public string Name { set; get; }
public int YearOld { set; get; }
public string Address { set; get; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var list = new List<Per>()
{
new Per() {Name = "Phạm Tuân",Title = "Quán quân" ,Address = "Phước Long B, Q9", YearOld = 21},
new Per() {Name = "Phạm Văn Chu", Title = "Á quân",Address = "Phước Long A, Q9", YearOld = 22}
};
listBox.ItemsSource = list; //ListBox trên
listBox2.ItemsSource = list; //ListBox dưới
}
}
[/code]
[caption id="attachment_294" align="aligncenter" width="660"]
No-DataTemplate[/caption]Nhưng tôi muốn hiển thị trong ListBox nhiều thông tin hơn thế thì làm thế nào? Tôi tiến hành thiết kế DataTemplate, tôi add dataTemplate cho ListBox , với mỗi Item của ListBox sẽ chứa 3 TextBox hiển thị 3 nội dung khác nhau được Binding đến 3 thuộc tính của một Item như sau trong ListBox.ItemSource.
[code language="xml"]
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Tiến hành thiết kế Start-->
<!--Thiết kế cho một Item của ListBox Start-->
<GroupBox Header="{Binding Title}"> <!--Binding đến thuộc tính Title-->
<StackPanel x:Name="panel" HorizontalAlignment="Stretch" >
<!--TextBox1 Binding đến thuộc tính Name-->
<TextBox Margin="2,2,2,0" Text="{Binding Name}"/>
<!--TextBox1 Binding đến thuộc tính YearOld-->
<TextBox Margin="2,2,2,0" Text="{Binding YearOld}" />
<!--TextBox1 Binding đến thuộc tính Address-->
<TextBox Margin="2,2,2,2" Text="{Binding Address}"/>
</StackPanel>
</GroupBox>
<!--Thiết kế cho một Item của ListBox End-->
<!-- Tiến hành thiết kế End-->
</DataTemplate>
</ListBox.ItemTemplate>
[/code]
Kết quả
Để cho đẹp hơn(theo cách cảu tôi), 3 TextBox kia tôi sẽ apply cho nó 3 ControlTemplate tôi đã thiết kế trước đó.
[code language="xml"]
<ListBox.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding Title}">
<StackPanel x:Name="panel" HorizontalAlignment="Stretch" >
<TextBox Margin="2,2,2,0" TextWrapping="Wrap" Tag="Name" BorderThickness="0"
Text="{Binding Name}" VerticalAlignment="Top" VerticalContentAlignment="Center"
BorderBrush="Green" Template="{DynamicResource TextBoxBase2}"
Background="{x:Null}" Padding="0,2"/>
<TextBox Margin="2,2,2,0" TextWrapping="Wrap" Tag="Year-Old"
Text="{Binding YearOld}" VerticalAlignment="Top" VerticalContentAlignment="Center"
BorderBrush="#FF007ACC" Template="{DynamicResource TextBoxBaseTemplate}"
Background="{x:Null}" Padding="0,2"/>
<TextBox Margin="2,2,2,2" TextWrapping="Wrap" Tag="Address"
Text="{Binding Address}" VerticalAlignment="Top" VerticalContentAlignment="Center"
BorderBrush="#FF007ACC" Template="{DynamicResource TextBoxBaseTemplate}"
Background="{x:Null}" Padding="0,2"/>
</StackPanel>
</GroupBox>
</DataTemplate>
</ListBox.ItemTemplate>
[/code]
Kết quả là
Các bạn có thể tham khảo sourcecode, hãy đoán đọc các kỹ thuật tiếp theo để có một Template thật sự linh hoạt và thân thiện.
Chúc các bạn thành công!
Phạm Tuân
Chủ Nhật, 28 tháng 12, 2014
Đa số các chip xử lý (CPU) hiện còn đang hoạt động mà chúng ta biết đến đều có trên hai nhân(lõi) và tính năng ảo hóa - điều này làm cho các máy tính có được tốc độ xử lý nhanh hơn và khả năng đa nhiệm tốt hơn. Chính vì vậy để tận dụng hết khả năng của CPU chúng ta cần xử lý đồng thời trên nhiều lõi CPU cho nhiều bài toán khác nhau để tiết kiệm thời gian và nâng cao hiệu xuất. Trong vấn đề trên và trong lập trình ứng dụng .NET nói riêng chúng ta có thuật ngữ Thread (luồng xử lý/tiểu trình), chúng ta sẽ tìm hiểu về nó trong bài viết này.
Bạn không nên nhầm lẫn giữa process (tiến trình) và thread (tiểu trình). Process có thể hiểu là một instance của chương trình máy tính được thực thi, dựa trên hệ điều hành, hoàn toàn độc lập với các tiến trình khác. Còn thread là một nhóm lệnh được tạo ra để thực thi một tác vụ trong một process, chúng chia sẻ chung dữ liệu với nhau để xử lý, điều này là cần thiết nhưng cũng là nguyên nhân dễ gây ra lỗi nếu bạn không xử lý đúng cách. Các bạn có thể tham khảo về Class Thread tại đây
[caption id="" align="aligncenter" width="735"]
Trong nội dung này tôi không nói sâu về Thread trong C#, các bạn có thể tìm hiểm thêm tại đây, tôi sẽ xoay quanh về cách Thread hoạt động với các thành phần của WPF, xong tôi sẽ vẫn nhắc các bạn nhớ về Thread và Cách tạo một Thread như sau
[code language="csharp"]
public void CreateThread()
{
Thread thread = new Thread(new ThreadStart(MethodJob));
//MethodJob có thể là một delegate
//Run thread
thread.Start();
//Buộc hoãn thực thi đến khi được phép
thread.Suspend();
//Gọi thread tiếp tục chạy
thread.Resume();
//Buộc thread phải dừng
thread.Abort();
//Đợi thread xử lý xong
thread.Join();
}
private void MethodJob()
{
for (int i = 0; i < 100; i++)
{
//Do something
}
}
[/code]
Vấn đề Thread trong WPF - Truy cập an toàn một Control
Trong winform, một Thread phụ được tạo ra không thể tự mình truy cặp được các Control của Thread chính một cách an toàn được, chúng ta sẽ phải dùng delegate, trong WPF cũng thế; Nhưng trong WPF, mỗi Control đều chứa một đối tượng là Dispatcher - đối tượng này giúp đồng bộ các truy cập đến Control hiện tại và tránh sảy ra lỗi(tương tực việc lock(object)). Vì vậy, khi muốn thực hiện một truy cập an toàn đến một Control bất kỳ chúng ta cần gọi Dispatcher của control đó và phương thức xử lý Invoke(delegate), phương thức này chứa một delegate để làm việc với các thuộc tính của Control.
Ví dụ dưới đây phục vụ nhu cầu đếm lần lược từ 0->99 và hiện số đó lên TextBox
[code language="csharp"]
private void BtnRun_OnClick(object sender, RoutedEventArgs e)
{
//thread là một biến toàn cục kiểu Thread
if (thread != null)
{
thread.Abort(); //Dừng công viện hiện tại
}
//delegate được tạo theo định nghĩa Lambeda
thread = new Thread(new ThreadStart(() =>
{
//nội dung delegate
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
//An toàn truy cập với Dispatcher
txtData.Dispatcher.Invoke(() => txtData.Text = i.ToString());
//Vùng giới hạn an toàn rộng hơn
//this.Dispatcher.Invoke(() => txtData.Text = i.ToString());
}
}));
thread.IsBackground = true;
thread.Start();
}
[/code]
Ví dụ dưới đây phục vụ nhu cầu đếm lần lược từ x->99 và hiện số đó lên TextBox, với x là một số được truyền vào, tôi truyền vào số 10. Lưu ý tôi có hai delegate(Counter,Counters) chịu trách nhiệm hiển thị số đếm lên TextBox nhưng chúng có cách hiển thị khác nhau, các bạn hãy chú ý về cách truyền đối số cho Thread và hai delegate. Tôi gọi phương thức Invoke(delegate, object[]).
[code language="csharp"]
private void BtnRun2_OnClick(object sender, RoutedEventArgs e)
{
if (thread2 != null)
{
thread2.Abort();
}
thread2 = new Thread(new ParameterizedThreadStart(Run2));
thread2.IsBackground = true;
thread2.Start(10);
}
private delegate void Counter(int num);
private delegate void Counters(int num,int limit);
private void Run2(object numStart)
{
for (int i = (int)numStart; i < 100; i++)
{
//Cách truyền một tham số
this.Dispatcher.Invoke(new Counter(StartCount), i);
//Cách truyền nhiều tham số
this.Dispatcher.Invoke(new Counters(StartCounts), new object[] {i, 40});
Thread.Sleep(800);
}
}
private void StartCount(int num)
{
if (num < 20)
{
txtData2.Text = num.ToString();
}
else
{
txtData2.Text = "---";
}
}
private void StartCounts(int num,int limit)
{
if (num > 30 && num < limit)
{
txtData2.Text = num.ToString();
}
}
[/code]
Vấn đề Thread trong WPF - Show một Window từ Thread phụ
Nếu nhu cầu là một thread phụ được tạo ra để thực hiện một công việc nào đó và trong đó cần Show một Window thì bạn sẽ làm thế nào. Nếu bạn có thói quen dùng delegate như đoạn code bênh dưới chắc chắn rằng bạn sẽ gặp lỗi "The calling thread must be STA, because many UI components require this."
[code language="csharp"]
private void BtnRun4_OnClick(object sender, RoutedEventArgs e)
{
Thread thread5 = new Thread(new ThreadStart(ShowSubForm));
thread5.IsBackground = true;
thread5.Start();
}
private void ShowSubForm()
{
new MainWindow().Show();
}
[/code]
Vậy ta khắc phục lỗi trên như sau, cần tạo ra một Thread STA
[code language="csharp"]
private void BtnRun4_OnClick(object sender, RoutedEventArgs e)
{
Thread thread5 = new Thread(new ThreadStart(ShowSubForm));
thread5.SetApartmentState(ApartmentState.STA);
thread5.IsBackground = true;
thread5.Start();
}
private void ShowSubForm()
{
new MainWindow().Show();
//Nói rằng đây là một Window độc lập
System.Windows.Threading.Dispatcher.Run();
}
[/code]
Chúng ta còn cách khác đơn giản hơn với Dispathcher, bạn hảy thử
[code language="csharp"]
private void BtnRun4_OnClick(object sender, RoutedEventArgs e)
{
Thread thread5 = new Thread(new ThreadStart(ShowSubForm));
thread5.IsBackground = true;
thread5.Start();
}
private void ShowSubForm()
{
this.Dispatcher.Invoke(() =>
{
new MainWindow().Show();
});
}
[/code]
Vậy là chúng ta đã biết thêm về các vấn đề về Thread trong WPF, các bạn có thể tham khảo SourceCode
Chúc các bạn thành công!
Phạm Tuân
Search
Popular Posts
-
Để dễ hiểu, các bạn hãy hình dung rằng: cùng một tập tin thực thi *.EXE nhưng bạn DoubleClick 10 lần cách quãng vào nó thì chuyện gì sẽ sảy ...
-
Phong cách thiết kế Material Design nhắm đến những đường nét đơn giản, sử dụng nhiều mảng màu đậm nổi bật, các đối tượng đồ họa trong giao ...
-
LOG4NET là một thư viện mã nguồn mở cho phép chúng ta tạo ra một hoặc nhiều tập tin log, kiểu log cũng như nội dung log một cách linh hoạt v...
-
ReSharper là một công cụ Add-on cho Visual Studio, nó giúp lập trình viên tốn ít thời gian cho việc xem xét định dạng code, phong cách code,...
-
Hôm nay chúng ta sẽ tìm hiểu cách tạo ra các menu chuột phải trên ứng dụng WPF của mình. Ở bài này chúng ta xoay quanh việc tìm hiểu cách tạ...
-
Hôm nay tôi sẽ giới thiệu với các bạn một Framework đến từ Microsoft - framework này không quá lớn nhưng là khá mạnh khi các bạn làm việc vớ...
-
Một khía cạnh hết sức quan trọng trong WPF đó là Trigger - Trigger giúp nắm bắt được mọi thay đổi trong một hệ thống GUI của WPF. Nhờ có Tr...
-
Như ở bài trước tôi đã giới thiệu về caliburn.mocro, bài tiếp theo này tôi sẽ hướng dẫn các bạn về cách tạo một project WPF hợp chuẩn mô hìn...
-
Khi các bạn làm một dự án khá lớn, nhu cầu tạo ra các Custom Control là không tránh khỏi, trong WinForm hay WPF đều cho phép chúng ta tạo r...
-
Hôm nay tôi sẽ giới thiệu đến các bạn một thư viện mã nguồn mở, nó là gì? Đơn giản là một thư viện với TabControl được Custom lại với nhiều ...




