Skip to content

การทำหน้าต่าง UI แบบมืออาชีพ (WPF)

TaskDialog.Show() เป็นคำสั่งที่ดีในการเช็กผลลัพธ์ไวๆ แต่เมื่อผู้ใช้ต้องการหน้าต่างให้กรอกข้อมูล, เลือก Dropdown, หรือติ๊ก Checkbox เราจะต้องการ User Interface (UI) ที่แท้จริง

ในโลกการพัฒนา .NET ยุคใหม่สำหรับการทำ Desktop App เราจะใช้เทคโนโลยีที่เรียกว่า WPF (Windows Presentation Foundation) ครับ

WPF ใช้ไฟล์ .xaml สำหรับออกแบบหน้าตา (คล้ายกับการเขียน HTML บนหน้าเว็บ) และมีไฟล์ Views.xaml.cs (Code-Behind) เป็นตัวควบคุมเหตุการณ์ต่างๆ

1. เตรียมโปรเจ็กต์ให้รองรับ WPF

Section titled “1. เตรียมโปรเจ็กต์ให้รองรับ WPF”

โดยปกติ Class Library ของ .NET จะไม่รู้จัก WPF เราต้องย้อนกลับไปทำโปรเจ็กต์ของเราก่อน

เปิดโฟลเดอร์โปรเจ็กต์ เปิดไฟล์ RevitToolkit.csproj

ให้เพิ่มคู่แท็ก <UseWPF>true</UseWPF> เข้าไปใน PropertyGroup:

โปรเจ็กต์.csproj
<PropertyGroup>
<!-- หากใช้ Revit 2027 อย่าลืมเปลี่ยนเป็น net10.0-windows -->
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PlatformTarget>x64</PlatformTarget>
<!-- เติมบรรทัดนี้ลงไปเพื่อเปิดใช้งาน WPF -->
<UseWPF>true</UseWPF>
</PropertyGroup>

2. เริ่มสร้างไฟล์ WPF (XAML)

Section titled “2. เริ่มสร้างไฟล์ WPF (XAML)”

ให้คุณสร้างไฟล์ชื่อ MainWindow.xaml ขึ้นมาในโปรเจ็กต์

MainWindow.xaml
<!-- MainWindow.xaml -->
<!-- 1. x:Class ต้องตรงกับชื่อ Namespace.ClassName ของไฟล์ Code-Behind (.cs) เสมอ -->
<Window x:Class="RevitToolkit.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ระบบรีเนียมเสาอัตโนมัติ" Height="250" Width="400"
WindowStartupLocation="CenterScreen">
<!-- 2. พื้นที่หลัก (Grid) สำหรับตีตารางวางคอนโทรลต่างๆ -->
<Grid Margin="20">
<!-- 3. จัดเรียงชิ้นส่วนแบบซ้อนลงมาเรื่อยๆ (แนวดิ่ง) -->
<StackPanel>
<TextBlock Text="กรอกคำนำหน้าย่อให้เสา (Prefix):" FontWeight="Bold" Margin="0,0,0,10"/>
<!-- 4. ตั้งชื่อตัวแปรให้ Textbox นี้ว่า txtPrefix เผื่อให้ C# เรียกหาได้ -->
<TextBox Name="txtPrefix" Height="30" FontSize="16" Text="C-"/>
<!-- 5. ใส่ปุ่มพร้อมโยง Event Click ไปที่ฟังก์ชัน BtnRun_Click ใน C# -->
<Button Name="btnRun" Content="เริ่มทำงาน" Height="40" Margin="0,20,0,0"
Background="#007ACC" Foreground="White" FontSize="16"
Click="BtnRun_Click"/>
</StackPanel>
</Grid>
</Window>

3. ผูกคำสั่งใน Code-Behind

Section titled “3. ผูกคำสั่งใน Code-Behind”

ไฟล์ MainWindow.xaml.cs จะมีหน้าตาประมาณนี้ เพื่อดักจับตอนที่ผู้ใช้กดปุ่ม btnRun

MainWindow.xaml.cs
using System.Windows;
namespace RevitToolkit;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BtnRun_Click(object sender, RoutedEventArgs e)
{
string userPrefix = txtPrefix.Text;
// โค้ดสำหรับติดต่อ Revit จะมาอยู่ตรงนี้
MessageBox.Show($"พร้อมรันเสาด้วยรหัสเริ่มต้น: {userPrefix}");
// ปิดหน้าต่างให้เรียบร้อย
this.Close();
}
}

4. เรียกหน้าจอจากการกดปุ่มใน Ribbon

Section titled “4. เรียกหน้าจอจากการกดปุ่มใน Ribbon”

กลับมาที่ไฟล์ Command.cs หรือจะเป็นปุ่ม Command ปุ่มใหม่ก็ได้ จุดที่เราเคยรันคำสั่งทำงาน ให้เปลี่ยนมาเป็นการเอาหน้าจอโชว์ขึ้นมาแทนแบบนี้:

Command.cs
[Transaction(TransactionMode.Manual)]
public class CommandUI : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
// 1. จำลองสร้างอ็อบเจกต์หน้าจอจากคลาสที่เราออกแบบไว้
MainWindow window = new MainWindow();
// 2. window.ShowDialog(): สั่งแสดงผลหน้าจอแบบ Modal Window
// สังเกตว่าเราใช้ ShowDialog() แทน Show() ธรรมดา
window.ShowDialog();
return Result.Succeeded;
}
}

🔍 เจาะลึกการรัน WPF UI บน Revit API

Section titled “🔍 เจาะลึกการรัน WPF UI บน Revit API”

การนำเทคโนโลยี Windows Presentation Foundation (WPF) เข้ามาผสานงานร่วมกับระบบปลั๊กอิน มีรายละเอียดทางเทคนิคที่สำคัญดังนี้ครับ:

1. ทำไมต้องเติม <UseWPF>true</UseWPF> ในไฟล์โปรเจ็กต์?

Section titled “1. ทำไมต้องเติม <UseWPF>true</UseWPF> ในไฟล์โปรเจ็กต์?”
  • คำอธิบาย: โดยปกติคลาสไลบรารี (.NET Class Library) ทั่วไปจะไม่มีการโหลดสิทธิ์การอ่านกราฟิกหน้าต่างสไตล์ WPF เข้ามาในคอมไพเลอร์ การระบุแท็กนี้จะไปปลดล็อกการอ้างอิงชุดคำสั่งสำหรับจัดการความสวยงามอย่าง PresentationCore, PresentationFramework, และ WindowsBase เข้ามาสู่โปรเจ็กต์แบบอัตโนมัติ

2. ความแตกต่างระหว่าง ShowDialog() และ Show()

Section titled “2. ความแตกต่างระหว่าง ShowDialog() และ Show()”
  • window.ShowDialog() (Modal Window):
    • พฤติกรรม: เปิดหน้าต่างออกมาและหยุด (Block) การทำงานของ Thread หลักชั่วคราว ทำให้ผู้ใช้ต้องกดยืนยันหรือปิดหน้าต่างปลั๊กอินนี้ลงเสียก่อน จึงจะสามารถกลับไปคลิกเลือกวัตถุบนหน้าจอ Revit ได้
    • ข้อดี: ปลอดภัยมาก เพราะในช่วงเวลาที่หน้าต่างนี้เปิดอยู่ จะไม่มีการคลิกขยับโมเดลใดๆ บน Revit โค้ดจึงรันได้อย่างเสถียร
  • window.Show() (Modeless Window):
    • พฤติกรรม: เปิดหน้าจอออกมาลอยๆ โดยที่ผู้ใช้สามารถเอาเมาส์จิ้มทำงานควบคู่ไปกับหน้าต่างโมเดลหลักของ Revit ได้
    • ข้อควรระวังสุดขีด: หากหน้าต่างลอยอยู่แล้วผู้ใช้คลิกปุ่มบนหน้าต่างเพื่อสั่งงาน Revit API ตรงๆ โปรแกรมจะดีดแครชทันทีเนื่องจากขัดต่อหลักการทำงาน Main Thread ของ Revit (ซึ่งเราจะไปแก้ด้วยกลไก IExternalEventHandler ในบทถัดไป)

ทั้งหมดนี้คือหลักการสร้าง UI เบื้องต้น แต่ในการทำงานที่มีความสลับซับซ้อน เรามักจะไม่เขียนโค้ดที่แตะต้อง Revit API ในไฟล์ .xaml.cs ตรงๆ เพราะมันจะมั่วมาก ไปใช้รูปแบบการแยกส่วนที่เรียกว่า MVVM ในบทถัดไปกันครับ!