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

Hiển thị các bài đăng có nhãn tuanphamdg. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn tuanphamdg. Hiển thị tất cả bài đăng

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

Chủ Nhật, 21 tháng 12, 2014

Delegate là một kiến thức căn bản cần nắm vững khi bạn làm việc với C#, .NET. Mặc dù bạn có thể tránh việc sử dụng delegate nhưng cuối cùng bạn vẫn phải công nhận rằng lập trình viên không thể không dùng delegate được. Delegate nói về mặt nào đó nó tương tự như con trỏ hàm trong C++, không khó hiểu cũng như không khó để áp dụng, nếu bạn dành một chút thời gian tìm hiểu.



Một delegate giống như một “người đại diện” hay “đại sứ”. Một delegate có thể được dùng để tạo một bao đóng (encapsulation) cho bất kì phương thức nào, miễn là nó phù hợp (kiểu trả về, tham số). Là một “đại sứ”, delegate có thể triệu gọi phương thức bất kì nơi nào: từ đối tượng này đến đối tượng kia, từ thread này sang thread kia,… Đây là đặc điểm chính của delegate, bạn sẽ cần nhớ lại điều này khi giải quyết các vấn đề thường gặp như truyền dữ liệu giữa hai Form, xử lý lỗi “Cross-thread operation not valid”, tạo event, …

 

Cách khai báo và sử dụng:


[code language="csharp"]
public delegate void Del(string message);

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}

// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");
[/code]

Bây giờ chúng ta sẽ sử dụng delegate để truyền dữ lệu giữa hai Form. Tôi có FormMain được xem là chính, chính form này sẽ mở FormChild, vấn đề làm sao để khi Click vào button trên formChild thì dữ liệu sẽ được gửi sang formMain và hiển thị lên textBox.

Cách làm là tại formChild ta định nghĩa một kiểu delegate với các kiểu dữ liệu phù hợp, trong class formChild khai báo thêm một biến(*) với kiểu delegate đã định nghĩa ở trên:

[code language="csharp"]
//Định nghĩa delegate
public delegate void DelSendMsg(string msg);

//khạ báo biến kiểu delegate
public DelSendMsg SendMsg;

public FormChild()
{
InitializeComponent();
}

private void BtnButton_OnClick(object sender, RoutedEventArgs e)
{
if (SendMsg != null)
{
// Thực thi hàm 'ShowMsg(string msg)' ở formMain
SendMsg.Invoke(TxTextBox.Text);
}
}
[/code]

Ở formMain khi new formChild() xong, tôi sẽ truyền vào cho formChild một phương thức của formMain thông qua biến (*) delegate ở formChild. Lưu ý tôi có hai Button dùng để Show formChild với cùng mục đích nhưng cách truyền con trỏ hàm khác nhau(tường minh và Lambda Expressions).

[code language="csharp"]
public FormMain()
{
InitializeComponent();
}

//Truyền con trỏ hàm tường minh
private void BtnButton_OnClick(object sender, RoutedEventArgs e)
{
FormChild child = new FormChild();
child.SendMsg = ShowMsg; //void ShowMsg(string msg)
child.Show();
}

//Hàm ủy nhiệm cho formChild
private void ShowMsg(string msg)
{
TxTextBox.Text = msg;
}

//Truyền con trỏ hàm với Lambda Expressions
private void BtnButton2_OnClick(object sender, RoutedEventArgs e)
{
FormChild child = new FormChild();
child.SendMsg = (msg) => TxTextBox.Text = msg;
child.Show();
}
[/code]

Vậy là khi "Show formChild", tại form này tôi nhập thông tin vào textBox và nhấn "Send to" thì lập tức textBox ở formMain sẽ thay đổi giá trị tương ứng. Kết quả:

[caption id="attachment_193" align="aligncenter" width="288"]delegate tuanpham Phạm Tuân[/caption]

Mời các bạn tham khảo SourceCode tại đây

Chúc các bạn thành công!
Phạm Tuân