WPF/C#:讓繪製的圖形可以被選中並將資訊顯示在ListBox中

mingupupup發表於2024-04-19

實現的效果

最後的實現效果

如果你對此感興趣,可以接著往下閱讀。

實現過程

繪製矩形

比如說我想繪製一個3行4列的表格:

 private void Button_Click_DrawRect(object sender, RoutedEventArgs e)
 {
     int Row = 3;
     int Col = 4;
     
     for(int i = 0; i < Row; i++)
     {
         for(int j = 0; j< Col; j++) 
         {
             // 新增矩形
             System.Windows.Shapes.Rectangle rectangle = new System.Windows.Shapes.Rectangle
             {
                 Width = 50,
                 Height = 50,
                 Stroke = System.Windows.Media.Brushes.Blue,

                 // 設定填充顏色為透明色
                 Fill = System.Windows.Media.Brushes.Transparent,
                 StrokeThickness = 1
             };
          
             Canvas.SetLeft(rectangle, 80 + 50 * j);
             Canvas.SetTop(rectangle, 50 + 50 * i);
           
             myCanvas1.Children.Add(rectangle);
            
         }
        
       
     }

實現的效果:

image-20240418110949637

現在又想畫4行3列的表格了,只需修改這裡:

int Row = 4;
int Col = 3;

實現的效果:

image-20240418111330852

為每個單元格新增資訊

繪製了單元格之後,我們想要在單元格中新增它所在的行與列的資訊。

在繪製矩形後面新增:

 // 在矩形內部新增文字
 TextBlock textBlock = new TextBlock
 {
     Text = i + "-" + j,
     Foreground = System.Windows.Media.Brushes.Black,
     FontSize = 12
 };

 Canvas.SetLeft(textBlock, 80 + 50 * j + 10);
 Canvas.SetTop(textBlock, 50 + 50 * i + 10);

 myCanvas1.Children.Add(textBlock);

現在實現的效果如下所示:

image-20240419084117023

讓每個單元格可以被選中與取消選中

我們設定滑鼠左鍵點選表示選中,滑鼠右鍵點選表示取消選中,選中之後,單元格邊框會變紅,取消選中後又恢復原來的顏色。

為每個單元格新增滑鼠點選事件處理程式:

 // 新增滑鼠事件處理器,左鍵點選表示選中
 rectangle.MouseLeftButtonDown += Rectangle_MouseLeftButtonDown;

 // 新增滑鼠事件處理器,右鍵點選表示取消選中
 rectangle.MouseRightButtonDown += Rectangle_MouseRightButtonDown;

滑鼠點選事件處理程式:

 // 滑鼠事件處理程式,左鍵點選表示選中
 private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 {   
     System.Windows.Shapes.Rectangle? rectangle = sender as System.Windows.Shapes.Rectangle;
     if (rectangle != null)
     {
         // 改變矩形的顏色以表示它被選中
         rectangle.Stroke = System.Windows.Media.Brushes.Red;             
     }
 }

  // 滑鼠事件處理器,右鍵點選表示選中
  private void Rectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
  {   
      System.Windows.Shapes.Rectangle? rectangle = sender as System.Windows.Shapes.Rectangle;
      if (rectangle != null)
      {
          // 改變矩形的顏色以表示它被取消選中
          rectangle.Stroke = System.Windows.Media.Brushes.Blue;
                     
      }
  }

現在檢視實現的效果:

點選單元格改變顏色的效果

將每個單元格與其中的資訊對應起來

在這裡可以發現每個單元格與其中的資訊是一一對應的關係,我們就可以採用字典這種資料結構。

Dictionary<System.Windows.Shapes.Rectangle, string> rectangleText = new Dictionary<System.Windows.Shapes.Rectangle, string>();
 // 將單元格與對應的資訊存入字典
 rectangleText[rectangle] = textBlock.Text;

這樣就實現了每個單元格與其中資訊的一一對應。

ListBox的使用

首先設計兩個類。

public class SelectedRect
{      
    public string? Name {  get; set; }
}

表示選中的單元格,只有一個屬性就是它所儲存的資訊。

public class SelectedRects : ObservableCollection<SelectedRect>
{

}

表示選中的多個單元格,繼承自ObservableCollection<SelectedRect>

ObservableCollection<T>是.NET框架中的一個類,它表示一個動態資料集合,當新增、刪除項或者重新整理整個列表時,它會提供通知。這對於資料繫結非常有用,因為當集合改變時,UI可以自動更新以反映這些更改。

 SelectedRects selectedRects;
 public Drawing()
 {
     InitializeComponent();
     this.selectedRects = new SelectedRects();
     DataContext = selectedRects;

 }

WPF(Windows Presentation Foundation)中,DataContext是一個非常重要的概念,它是資料繫結的基礎。
DataContext是定義在FrameworkElement類中的一個屬性,幾乎所有的WPF控制元件都繼承自FrameworkElement,因此幾乎所有的WPF控制元件都有DataContext屬性。
DataContext屬性通常被設定為一個物件,這個物件包含了繫結到介面元素的資料。當你在XAML中建立資料繫結時,繫結表示式會查詢DataContext中的屬性。

需要注意的是,DataContext是可以繼承的,如果一個元素的DataContext沒有被顯式設定,它將使用其父元素的DataContext。這使得你可以在視窗級別設定DataContext,然後在視窗的所有子元素中使用資料繫結。

在這裡我們就是這樣設定了視窗的DataContext屬性為selectedRects

現在我們修改點選事件處理程式:

 private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 {
     System.Windows.Shapes.Rectangle? rectangle = sender as System.Windows.Shapes.Rectangle;
     if (rectangle != null)
     {
         // 改變矩形的顏色以表示它被選中
         rectangle.Stroke = System.Windows.Media.Brushes.Red;
         
         string text = rectangleText[rectangle];
         
         SelectedRect selectedRect = new SelectedRect();
         selectedRect.Name = text;
         selectedRects.Add(selectedRect);
        
     }

 }

 private void Rectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
 {
     System.Windows.Shapes.Rectangle? rectangle = sender as System.Windows.Shapes.Rectangle;
     if (rectangle != null)
     {
         // 改變矩形的顏色以表示它被取消選中
         rectangle.Stroke = System.Windows.Media.Brushes.Blue;
         
         string text = rectangleText[rectangle];
         
         var selectedRect = selectedRects.Where(x => x.Name == text).FirstOrDefault();
         if (selectedRect != null)
         {
             selectedRects.Remove(selectedRect);
         }


     }

 }

在ListBox設定資料繫結:

 <ListBox Grid.Column="1" SelectedIndex="0" Margin="10,0,10,0"
           ItemsSource="{Binding}">
    
 </ListBox>

現在來看看效果:

image-20240419093037392

我們會發現在ListBox中只會顯示類名,並不會顯示類中的資訊。

這是為什麼呢?

因為我們只設定了資料繫結,ListBox知道它的資料來自哪裡了,但是我們沒有設定資料模板,ListBox不知道該按怎樣的方式顯示資料。

資料模板的使用

現在我們就來設定一下資料模板,先來介紹一下資料模板。

WPF(Windows Presentation Foundation)中,資料模板(DataTemplate)是一種定義資料視覺表示的方式。它允許你自定義如何顯示繫結到控制元件的資料。

資料模板非常強大,它可以包含任何型別的元素,並可以使用複雜的繫結和樣式。透過使用資料模板,你可以建立豐富和個性化的UI,而無需在程式碼中手動建立和管理元素。

現在開始嘗試去使用資料模板吧。

在xaml中新增:

<Window.Resources>
    <DataTemplate x:Key="MyTemplate">
        <TextBlock  Text="{Binding Path=Name}"/>
    </DataTemplate>
</Window.Resources>

<Window.Resources>:這是一個資源字典,它包含了在整個視窗中都可以使用的資源。在這個例子中,它包含了一個資料模板。
<DataTemplate x:Key="MyTemplate">:這定義了一個資料模板,並給它指定了一個鍵"MyTemplate"。這個鍵可以用來在其他地方引用這個模板。
<TextBlock Text="{Binding Path=Name}"/>:這是資料模板的內容。它是一個TextBlock,其Text屬性繫結到資料物件的Name屬性。{Binding Path=Name}是一個繫結表示式,它告訴WPF查詢資料物件中名為Name的屬性,並將其值繫結到TextBlock的Text屬性。

ListBox使用這個資料模板:

<ListBox Grid.Column="1" SelectedIndex="0" Margin="10,0,10,0"
          ItemsSource="{Binding}"
         ItemTemplate="{StaticResource MyTemplate}">
   
</ListBox>

現在再來看一下效果:

顯示效果

發現可以正常顯示資料了,但是還有一個問題,就是會重複新增,最後解決這個問題就好了!

修改滑鼠左鍵點選事件處理程式:

 // 滑鼠事件處理器,左鍵點選表示選中
 private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 {
     System.Windows.Shapes.Rectangle? rectangle = sender as System.Windows.Shapes.Rectangle;
     if (rectangle != null)
     {
         // 改變矩形的顏色以表示它被選中
         rectangle.Stroke = System.Windows.Media.Brushes.Red;
         string text = rectangleText[rectangle];
         if (selectedRects.Where(x => x.Name == text).Any())
         {

         }
         else
         {
             SelectedRect selectedRect = new SelectedRect();
             selectedRect.Name = text;
             selectedRects.Add(selectedRect);
         }

     }

 }

現在再來看看最後的效果:

最後的效果

全部程式碼

xaml:

<Window x:Class=""
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local=""
        xmlns:hc="https://handyorg.github.io/handycontrol"
        mc:Ignorable="d"
        Title="Drawing" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="MyTemplate">
            <TextBlock  Text="{Binding Path=Name}"/>
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <hc:Row Margin="0,20,0,0">
            <hc:Col Span="8">
                <Label Content="畫矩形"></Label>
            </hc:Col>
            <hc:Col Span="8">
                <Button Style="{StaticResource ButtonPrimary}" Content="開始"
         Click="Button_Click_DrawRect"/>
            </hc:Col>
            <hc:Col Span="8">
                <Button Style="{StaticResource ButtonPrimary}" Content="清空"
                        Click="Button_Click_Clear"/>
            </hc:Col>
        </hc:Row>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Canvas Grid.Column="0" Background="Azure" x:Name="myCanvas1" Height="400">
                <!-- 在這裡新增你的元素 -->
            </Canvas>


            <ListBox Grid.Column="1" SelectedIndex="0" Margin="10,0,10,0"
                      ItemsSource="{Binding}"
                     ItemTemplate="{StaticResource MyTemplate}">
               
            </ListBox>
        </Grid>



    </StackPanel>
</Window>

cs:

namespace xxx
{
    /// <summary>
    /// Drawing.xaml 的互動邏輯
    /// </summary>
    public partial class Drawing : System.Windows.Window
    {
        Dictionary<System.Windows.Shapes.Rectangle, string> rectangleText = new Dictionary<System.Windows.Shapes.Rectangle, string>();
        SelectedRects selectedRects;
        public Drawing()
        {
            InitializeComponent();
            this.selectedRects = new SelectedRects();
            DataContext = selectedRects;

        }

        private void Button_Click_DrawRect(object sender, RoutedEventArgs e)
        {
            int Row = 4;
            int Col = 3;
            
            for(int i = 0; i < Row; i++)
            {
                for(int j = 0; j< Col; j++) 
                {
                    // 新增矩形
                    System.Windows.Shapes.Rectangle rectangle = new System.Windows.Shapes.Rectangle
                    {
                        Width = 50,
                        Height = 50,
                        Stroke = System.Windows.Media.Brushes.Blue,

                        // 設定填充顏色為透明色
                        Fill = System.Windows.Media.Brushes.Transparent,
                        StrokeThickness = 1
                    };

                    // 新增滑鼠事件處理器,左鍵點選表示選中
                    rectangle.MouseLeftButtonDown += Rectangle_MouseLeftButtonDown;

                    // 新增滑鼠事件處理器,右鍵點選表示取消選中
                    rectangle.MouseRightButtonDown += Rectangle_MouseRightButtonDown;

                    Canvas.SetLeft(rectangle, 80 + 50 * j);
                    Canvas.SetTop(rectangle, 50 + 50 * i);
                  
                    myCanvas1.Children.Add(rectangle);

                    // 在矩形內部新增文字
                    TextBlock textBlock = new TextBlock
                    {
                        Text = i + "-" + j,
                        Foreground = System.Windows.Media.Brushes.Black,
                        FontSize = 12
                    };

                    Canvas.SetLeft(textBlock, 80 + 50 * j + 10);
                    Canvas.SetTop(textBlock, 50 + 50 * i + 10);

                    myCanvas1.Children.Add(textBlock);

                    // 將單元格與對應的資訊存入字典
                    rectangleText[rectangle] = textBlock.Text;
                }
               
              
            }
          

        }

        private void Button_Click_Clear(object sender, RoutedEventArgs e)
        {
            myCanvas1.Children.Clear();
        }

        // 滑鼠事件處理器,左鍵點選表示選中
        private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            System.Windows.Shapes.Rectangle? rectangle = sender as System.Windows.Shapes.Rectangle;
            if (rectangle != null)
            {
                // 改變矩形的顏色以表示它被選中
                rectangle.Stroke = System.Windows.Media.Brushes.Red;
                string text = rectangleText[rectangle];
                if (selectedRects.Where(x => x.Name == text).Any())
                {

                }
                else
                {
                    SelectedRect selectedRect = new SelectedRect();
                    selectedRect.Name = text;
                    selectedRects.Add(selectedRect);
                }

            }

        }

        // 滑鼠事件處理器,右鍵點選表示選中
        private void Rectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            System.Windows.Shapes.Rectangle? rectangle = sender as System.Windows.Shapes.Rectangle;
            if (rectangle != null)
            {
                // 改變矩形的顏色以表示它被取消選中
                rectangle.Stroke = System.Windows.Media.Brushes.Blue;
                string text = rectangleText[rectangle];
                var selectedRect = selectedRects.Where(x => x.Name == text).FirstOrDefault();
                if (selectedRect != null)
                {
                    selectedRects.Remove(selectedRect);
                }


            }

        }
    }
}

總結

本文透過一個小示例,跟大家介紹瞭如何在WPF上繪製矩形,並在其中新增文字,同時也介紹了ListBox的使用,透過資料繫結與資料模板顯示我們選中的單元格內的文字資訊。希望對與我一樣正在學習WPF或者對WPF感興趣的同學有所幫助。

相關文章