Trong bài này chúng ta sẽ tìm hiểu:

  • Tạo thực đơn (menu) cho ứng dụng WPF với các lớp MenuMenuItem
  • Xử lý sự kiện khi người dùng chọn thực đơn
  • Tạo thực đơn ngữ cảnh (pop up menu) với lớp ContextMenu
  • Sử dụng một số hộp thoại phổ biến

Thực đơn (menu) và các sự kiện thực đơn (menu events)

WPF cung cấp điều khiển Menu để có thể tạo thực đơn một cách dễ dàng trong chế độ Design và dùng mã XAML. Chúng ta cũng có thể viết mã C# để thực hiện lệnh khi người dùng nhấn vào một tuỳ chọn trên thanh thực đơn.

Để hiểu hơn về cách tạo thực đơn chúng ta sẽ thực hành tạo và xử lý sự kiện cho thực đơn trên ứng dụng Middleshire Bell Ringers Association Members mà chúng ta đã tạo ra từ chương XVII.

Tạo thực đơn dùng điều khiển Menu

Trước khi thêm điều khiển Menu vào form, chúng ta cần thêm điều khiển DockPanel từ thanh Toolbox trong mục All WPF Controls. DockPanel, giống như Grid hay StackPanel, tạo bố cục (layout) cho form và việc thêm Menu vào DockPanel là cho việc xử lý được dễ dàng. Kéo DockPanel đặt vào form (không trùng với điều khiển nào). Trong cửa sổ Properties của DockPanel, điều chỉnh thuộc tính Width đến Auto, HorizontalAlignment đến Stretch, VerticalAlignment đến Top, Margin đến 0. Mã XAML cho DockPanel như sau:

<DockPanel Name="docPanel1" HorizontalAlignment="Stretch" Height="100" 
           Margin="0,0,0,0" VerticalAlignment="Top" Width="Auto"/>

Từ thanh Toolbox trong mục All WPF Controls chọn điều khiển Menu và đặt vào trong DockPanel. Sử dụng cửa sổ Properties hay mã XAML để thay đổi các thuộc tính của Menu như sau:

<DockPanel Name="docPanel1" HorizontalAlignment="Stretch" Height="100" Margin="0,0,0,0" 
           VerticalAlignment="Top" Width="Auto">
<Menu Name="menu1" Height="23" VerticalAlignment="Top" Width="Auto" DockPanel.Dock="Top"/>
</DockPanel>

Hiện tại Menu của chúng ta chưa có bất cứ tuỳ chọn nào (menu item). Thêm các tuỳ chọn đến Menu bằng cách mở cửa sổ Properties của Menu và tìm đến thuộc tính Items. Tại thuộc tính Items, nhấp chuột vào nút bên phải để mở cửa sổ Collection Editor:Items

Thanh thực đơn chúng ta đã có hai tuỳ chọn là FileHelp. Bây giờ chúng ta sẽ thêm các tuỳ chọn con cho FileHelp bằng mã XAML. Thay đổi mã XAML cho hai phần tử MenuItem như sau:

<DockPanel ...>
   <Menu ...>
      <MenuItem Header="_File"></MenuItem>
      <MenuItem Header="_Help"></MenuItem>
   </Menu>
</DockPanel>

Thêm các MenuItem đến MenuItem File như đoạn mã sau:

<MenuItem Header="_File">
   <MenuItem Header="_New Member" Name="newMember" />
   <MenuItem Header="_Save Member Details" Name="saveMember" />
   <Separator/>
   <MenuItem Header="E_xit" Name="exit" />
</MenuItem>

Thêm MenuItem đến MenuItem Help như sau:

<MenuItem Header="_Help">
   <MenuItem Header="_About Middleshire Bell Ringers" Name="about" />
</MenuItem>

Lưu tất cả và thực thi ứng dụng chúng ta sẽ thấy các tuỳ chọn con cho hai tuỳ chọn FileHelp:

Chúng ta có thể thêm các biểu tượng (icon) đến các tuỳ chọn bằng cách dùng phần tử MenuItem.Icon. Ví dụ chúng ta muốn thêm icon đến tuỳ chọn New Member như sau:

<MenuItem Header="_New Member" Name="newMember">
   <MenuItem.Icon>
    <Image Source="hotface.png"/>
   </MenuItem.Icon>
</MenuItem>

Lưu ý chúng ta phải thêm tập tin hotface.png đến dự án bằng cách nhấp chuột phải vào BellRingers chọn Add > Existing Item. Kết quả:

Có thể ap dụng tập thuộc tính bellRingersFontStyle đến điều khiển Menu:

<Menu Style="{StaticResource bellRingersFontStyle}" Name="menu1"  
     Height="23" VerticalAlignment="Top" Width="Auto" DockPanel.Dock="Top">

Giống như các điều khiển khác, như button, chúng ta cần xử lý các sự kiện cho các tuỳ chọn của thanh thực đơn. Cụ thể, chúng ta cần cần xử lý sự kiện Click cho các tuỳ chọn con của FileHelp.

Xử lý sự kiện Menu

Trong khung XAML, thêm thuộc tính isEanabled với giá trị false đến các điều khiển firstName, lastName, towerNames, isCaptain, memberSince, yearsExperience, methods, clear và tuỳ chọn menu saveMember. Lúc này, tạm thời chúng ta sẽ không sử dụng được các điều khiển này.

Thêm thuộc tính Click đến MenuItem newMember, chọn <New Event Handler> xuất hiện khi chúng ta gõ dấu = và phương thức newMember_Click sẽ tự động xuất hiện trong cửa sổ View Code (lưu ý chúng ta có thể đổi tên phương thức cho thuộc tính Click và trong View Code):

Mã XAML

 
  <MenuItem Header="_New Member" 
            Name="newMember" 
            Click="newMember_Click"> 

Mã C#


private void newMember_Click(object sender, 
          RoutedEventArgs e) { }

Thêm đoạn mã cho phương thức newMember_Click như sau:

private void newMember_Click(object sender, RoutedEventArgs e)
{
   this.Reset();
   saveMember.IsEnabled = true;
   firstName.IsEnabled = true;
   lastName.IsEnabled = true;
   towerNames.IsEnabled = true;
   isCaptain.IsEnabled = true;
   memberSince.IsEnabled = true;
   yearsExperience.IsEnabled = true;
   methods.IsEnabled = true;
   clear.IsEnabled = true;
}

Đoạn mã gọi phương thức Reset và cho phép tất cả các điều khiển bị vô hiệu hoá được sử dụng.

Xử lý sự kiện Click cho MenuItem Exit như sau:

Mã XAML

MenuItem Header="E_xit" 
         Name="exit" 
         Click="exit_Click"/>

Mã C#

private void exit_Click(object sender, RoutedEventArgs e)
{
  this.Close();
}

Xử lý sự kiện Click cho MenuItem saveMember như sau:

Mã XAML

<MenuItem  IsEnabled="False" 
           Header="_Save Member Details" 
           Name="saveMember" 
           Click="saveMember_Click"/>

Mã C#


private void saveMember_Click(object sender, RoutedEventArgs e)

{

  using (StreamWriter writer = new StreamWriter("Members.txt"))

  {

    writer.WriteLine("First Name: {0}", firstName.Text);

    writer.WriteLine("Last Name: {0}", lastName.Text);

    writer.WriteLine("Tower: {0}", towerNames.Text);

    writer.WriteLine("Captain: {0}", isCaptain.IsChecked.ToString());

    writer.WriteLine("Member Since: {0}", memberSince.Text);

    writer.WriteLine("Methods: ");

    foreach (CheckBox cb in methods.Items)

    {

      if (cb.IsChecked.Value)

      {

        writer.WriteLine(cb.Content.ToString());

      }

    }

    MessageBox.Show("Member details saved", "Saved");

  }

}

Chúng ta sẽ lưu các thông tin về thành viên mới trong tập tin Members.txt bằng cách sử dụng đối tượng StreamWriter. Sử dụng MessageBox đến người dùng nếu thông tin được lưu thành công.

Khi người dùng vào Help và chọn MenuItem about, một hộp thoại sẽ xuất hiện để cung cấp một số thông tin về ứng dụng. Để làm điều này chúng ta cần thêm một form mới đến dự án và xử lý sự kiện Click.

Vào PROJECT >Add Window…Chọn Window (WPF) và gõ About.xaml trong ô Name:

Nhấn nút Add. Lúc này chúng ta có thể làm việc trên form chính MainWindow hay form About từ cửa sổ Solution Explorer:

Trong cửa sổ XAML của About, điều chỉnh các thuộc tính cho phần tử Window BellRingers.About và thêm các phần tử điều khiển như sau:

<Window x:Class="BellRingers.About" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="About Middleshire Bell Ringers" 
        Height="160" 
        Width="300" 
        Name="AboutBellRingers" 
        ResizeMode="NoResize">
   <Grid>
      <Label Content="Version 1.0" Height="28" 
             HorizontalAlignment="Left" Margin="30,20,0,0" 
             Name="version" VerticalAlignment="Top" Width="75" />
      <Label Content="Build date: January 2018 by ngocminhtran.com" Height="28" 
             HorizontalAlignment="Left" Margin="30,50,0,0" 
             Name="buildDate" VerticalAlignment="Top" Width="auto" />
      <Button Content="OK" Height="23" HorizontalAlignment="Left" 
             Margin="100,85,0,0" Name="ok" VerticalAlignment="Top" Width="78" />
   </Grid>
</Window>

Form About trông như sau:

Xử lý sự kiện cho button OK :

Mã XAML

<Button ... Click="ok_Click"/>

Mã C#


private void ok_Click(object sender, RoutedEventArgs e) { 

   this.Close(); 
}

Trở lại form chính bằng cách chọn MainWindow.xaml, xử lý sự kiện Click cho MenuItem about:

Mã XAML

<MenuItem Header="_About Middleshire Bell Ringers" 
          Name="about" Click="about_Click" />

Mã C#

 
private void about_Click(object sender, RoutedEventArgs e) 
{ 
   About aboutWindow = new About(); 
   aboutWindow.ShowDialog(); 
} 

Gọi form About bằng cách tạo một thể hiện của lớp About và dùng phương thức ShowDialog để hiển thị.

Lưu tất cả. Thực thi  ứng dụng. Từ File chọn New Member và điền các thông tin:

Từ File chọn Save Member Details:

Nhấn OK. Lúc này thông tin về thành viên mới đã được lưu trong tập tin Members.txt. Có thể tìm thấy tập tin bằng cách vào thư mục chứa dự án, vào thư mục BellRingers > bin > Debug.

Lúc này nút Add đã trở nên thừa và chúng ta  có thể xoá nó khỏi form (và các đoạn mã liên quan).

Tạo thực đơn ngữ cảnh (pop up menu)

Thực đơn ngữ cảnh (context menu hay pop up menu) là thực đơn gắn liền với một điều khiển nào đó hay form và sẽ xuất hiện khi chúng ta nhấp chuột phải. Trong bài thực hành kế tiếp chúng ta sẽ  tạo thực đơn ngữ cảnh cho hai textbox là firstNamelastName.

Tạo thực đơn ngữ cảnh

Trong khung XAML của MainWindow.xaml, thêm phần tử ContextMenu đến phần tử Window.Resource:

</Window.Resources>
...
  <ContextMenu x:Key="textBoxMenu" 
          Style="{StaticResource bellRingersFontStyle}" >
  </ContextMenu>
</Window.Resources>

ContextMenu, cũng giống như Menu, chứa một collection các MenuItem. Chúng ta thêm một MenuItemHeaderClear Name đến ConextMenu như sau:

<ContextMenu x:Key="textBoxMenu" Style="{StaticResource bellRingersFontStyle}" >
   <MenuItem Header="Clear Name" Name="clearName" />
</ContextMenu>

Thêm ContextMenu vừa định nghĩa đến hai textbox firstNamelastName bằng cách thêm thuộc tính ContextMenu đến hai textbox như sau:

<TextBox Name ="firstName"  ContextMenu="{StaticResource textBoxMenu}".../>
<TextBox Name ="lastName"  ContextMenu="{StaticResource textBoxMenu}".../>

Xử lý sự kiện Click cho MenuItem Clear Name như sau:

Mã XAML

<ContextMenu x:Key="textBoxMenu" Style="{StaticResource bellRingersFontStyle}" >
  <MenuItem Header="Clear Name" Name="clearName" Click="clearName_Click"/>
</ContextMenu>

Mã C#

 
private void clearName_Click(object sender, RoutedEventArgs e) 
{ 
  firstName.Clear(); 
  lastName.Clear(); 
} 

Lưu tất cả và thực thi ứng dụng. Chọn File > New Member. Gõ nội dung vào textbox First Name và kích chuột phải vào textbox này:

Một context menu sẽ xuất hiện với một tuỳ chọn là Clear Name. Khi nhấn chuột trái Clear Name thì nội dung trong textbox First Name biến mất.

Trong bài thực hành trên chúng ta dễ dàng tạo một ContextMenu cho các điều khiển bằng mã XAML và chúng ta  cũng có thể tạo ContextMenu bằng mã C#. Bài thực hành sau sẽ minh hoạ cách tạo ContextMenu cho form dùng mã C#.

Tạo ContextMenu với mã C#

Trong chế độ View Code của MainWindow, thêm một biến đến lớp MainWindow như sau:

public partial class MainWindow : Window
{
  ...
  private ContextMenu windowContextMenu = null;
  ...
}

Thêm đoạn mã sau đến phương thức khởi tạo MainWindow. Đoạn mã tạo một MenuItemHeaderSave Member Details và xử lý sự kiện Click bằng cách dùng delegate RoutedEventHandler để gọi phương thức saveMember_Click:

public MainWindow()
{
  InitializeComponent();
  this.Reset();
  MenuItem saveMemberMenuItem = new MenuItem();
  saveMemberMenuItem.Header = "Save Member Details";
  saveMemberMenuItem.Click += new RoutedEventHandler(saveMember_Click);
}

Thêm đoạn mã khác đến phương thức khởi tạo MainWindow để tạo một MenuItemHeaderClear Form và xử lý sự kiện Click bằng cách gọi phương thức clear_Click:

public MainWindow()
{
  ...
  MenuItem clearFormMenuItem = new MenuItem();
  clearFormMenuItem.Header = "Clear Form";
  clearFormMenuItem.Click += new RoutedEventHandler(clear_Click);
}

Sau khi đã có hai MenuItem, chúng ta sẽ tạo một ContextMenu chứa hai MenuItem trên bằng cách thêm đoạn mã sau vào phương thức khởi tạo MainWindow:

public MainWindow()
{
  ...
  windowContextMenu = new ContextMenu();
  windowContextMenu.Items.Add(saveMemberMenuItem);
  windowContextMenu.Items.Add(clearFormMenuItem);
}

Gán ContextMenu đến form khi chúng ta chọn New Member từ File bằng cách thêm đoạn mã sau đến cuối phương thức newMember_Click:

private void newMember_Click(object sender, RoutedEventArgs e)
{
  ...
  this.ContextMenu = windowContextMenu;
}

Lưu tất cả và thực thi chương trình. Vào File chọn New Member và kích chuột phải vào vị trí bất kỳ trên form (trừ các điều khiển):

Sử dụng các hộp thoại Windows

Trong ứng dụng BellRingers, chúng ta đã sử dụng đối tượng StreamWriter để lưu các thông tin thànhviên vào tập tin Members.txt. Tuy nhiên, chúng ta cần lưu thông tin hay mở tập tin theo cách của các ứng dụng Windows thường làm – nghĩa là dùng các hộp thoại. Thư viện .NET cung cấp cho chúng ta các lớp để thực hiện điều này một cách dễ dàng. Hai lớp phổ biến là OpenFileDialogSaveFileDialog để mở tập tin và lưu thông tin trong ứng dụng.

Trong bài thực hành kế tiếp chúng ta sẽ dùng lớp SaveFileDialog lưu thông tin trong ứng dụng BellRingers. Cách dùng OpenFileDialog hoàn toàn tương tự và có thể tham khảo tại thư viện MSDN hay một ví dụ tại đây.

Sử dụng lớp SaveFileDialog

Vào cửa sổ View Code của MainWindow và khai báo namespace Microsoft.Win32 chứa lớp SaveFileDialog:

using Microsoft.Win32;

Trong phương thức saveMember_Click, chèn đoạn mã sau vào vị trí đầu tiên của phương thức:

private void saveMember_Click(object sender, RoutedEventArgs e)
{
  SaveFileDialog saveDialog = new SaveFileDialog();
  saveDialog.DefaultExt = "txt";
  saveDialog.AddExtension = true;
  saveDialog.FileName = "Members";
  saveDialog.InitialDirectory = @"C:\Users\Public\Public Documents\";
  saveDialog.OverwritePrompt = true;
  saveDialog.Title = "Bell Ringers";
  saveDialog.ValidateNames = true;
  using (StreamWriter writer = new StreamWriter("Members.txt"))
  {
   ...
  }
}

Lệnh đầu tiên là khai báo khơi tạo một đối tượng của lớp SaveFileDialog. Lớp SaveFileDialog có một số thuộc tính quan trọng:

Thuộc tính Mô tả
DefaultExt Xác định phần mở rộng mặc định cho tập tin nếu người dùng không cung cấp phần mở rộng cho tập tin.
AddExtension Cho phép hộp thoại thêm phần mở rộng mặc định đến tên tập tin nếu người dùng bỏ qua phần mở rộng.
FileName Tên mặc định của tập tin.
InitialDirectory Thư mục mặc định khi hộp thoại mở.
OverwritePrompt Phát sinh cảnh báo khi người dùng tạo tập tin có tên trùng với một tập tin có sẵn. Mặc định là true.
Title Tiêu đề của hộp thoại.
ValidateNames Xác nhận tên của tập tin là hợp lệ.

Thêm lệnh if chứa toàn bộ khối lệnh using đã có trước đó của phương thức saveMember_Click:

if (saveDialog.ShowDialog().Value)
{
  using (StreamWriter writer = new StreamWriter("Members.txt"))
  {
   ...
  }
}

Lúc này đối tượng StreamWriter sẽ nhận tập tin từ đối tượng SaveFileDialog nên chúng ta cần thay đổi lệnh khởi tạo đối tượng StreamWriter như sau:

if (saveDialog.ShowDialog().Value)
{
  using (StreamWriter writer = new StreamWriter(saveDialog.FileName))
  {
   ...
  }
}

Lưu tất cả và thực thi ứng dụng. Nhập thông tin về thành viên mới và chọn File > Save Member Details, một hộp thoại sẽ xuất hiện:

Thay đổi đường dẫn, tên tập tin (và phần mở rộng nếu cần) và nhấn Save.

Học C# và WPF >