在 WPF 中使用 Window Forms 繪圖

  • 654
  • 0
  • 2022-01-27

WPF 中可以嵌入 Windows Forms 的控制項,理所當然也會有使用 Windows Forms 繪圖 API 的需求,這一篇要談的是在 MVVM Pattern 的狀況下,如何達成這樣的需求。

裝載 Windows Forms 控制項

首先我們要裝載一個 Windows.Forms.Panel 當作繪圖的畫板,當 WPF 裝載 Windows Forms 控制項的時候,需要加入兩個相關的組件 (1) WindowsFormsIntegration (2) System.Windows.Forms。

接著在 xaml 中定義 System.Windows.Forms 的 xml 命名空間,這當然是因為 Panel class 的命名空間就是 System.Windows.Forms。

xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

然後把 Grid 切成兩個 Rows, 用 WindowsFormsHost 標籤來裝載 Windows Forms 控制項。

<WindowsFormsHost>
    <wf:Panel x:Name="winFormPanel" />
</WindowsFormsHost>

到目前為止,整個 xaml 應該會長這樣:

<Window x:Class="WindowsFormsDrawInWPF.MainWindow"
        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="clr-namespace:WindowsFormsDrawInWPF"
        xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <WindowsFormsHost>
            <wf:Panel x:Name="winFormPanel"/>
        </WindowsFormsHost>
    </Grid>
</Window>
讓 View Model 認識控制項

既然要符合 MVVM 的樣子,那麼繪圖的行為就會放在 ViewModel 裡,所以得讓 ViewModel 認識那個 Panel。有幾種不同的方式,這邊我採用屬性傳入 (這個不需要甚麼屬性變更通知的):

 public class MainViewModel : NotifyPropertyBase
 {
     public System.Windows.Forms.Panel WinPanel { get; set; }
 }

在 xaml 上設定 DataContext:

<Window.DataContext >
    <local:MainViewModel WinPanel="{x:Reference winFormPanel}"/>
</Window.DataContext>
繪圖

簡單利用個 Button 的 Command 繪製圓形在 winFormPanel 上,因為要進行 GDI+ 繪圖,請先加入 System.Drawing 組件,接著建立 Command 與相關程式碼:

  public ICommand DrawCommand
  {
      get
      {
          return new RelayCommand((x) =>
          {
              if (WinPanel == null) { return; }
              Draw(System.Drawing.Color.Blue, new System.Drawing.Rectangle(0, 0, 100, 100));
          });
      }
  }

  private void Draw(System.Drawing.Color color, System.Drawing.Rectangle rect)
  {
      using (var myBrush = new System.Drawing.SolidBrush(color))
      {
          using (var formGraphics = WinPanel.CreateGraphics())
          {
              formGraphics.Clear(System.Drawing.Color.White);
              formGraphics.FillEllipse(myBrush, rect);
          }
      }
  }

完成 xaml 上的 Button 與命令繫結,整個 xaml 如下:

<Window x:Class="WindowsFormsDrawInWPF.MainWindow"
        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="clr-namespace:WindowsFormsDrawInWPF"
        xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext >
        <local:MainViewModel WinPanel="{x:Reference winFormPanel}"/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <WindowsFormsHost>
            <wf:Panel x:Name="winFormPanel"/>
        </WindowsFormsHost>
        <Button Grid.Row="1" Content="Draw" Command="{Binding DrawCommand}"/>
    </Grid>
</Window>

大功告成,完整的範例可在此下載