首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# Windows Forms 開發實用技巧精選:單一實例、MDI 子視窗管理、漸變動畫、Enter 鍵導航

     編輯:關於C語言

技巧一:確保應用程式單一實例執行

使用 Mutex 命名核心物件,跨進程同步,比 Process.GetProcessesByName 更可靠、無競爭條件。


static class Program
{
    [STAThread]
    static void Main()
    {
        // 全域唯一名稱,建議用 GUID 或 "Company.App.Name"
        using var mutex = new Mutex(true, "Global\\MyApp_UniqueId_{12345678-ABCD-EF01-2345-6789ABCDEF00}", out bool createdNew);
        
        if (!createdNew)
        {
            // 已有實例執行中:可選擇喚醒既有視窗 (需 IPC) 或直接提示退出
            MessageBox.Show("應用程式已在執行中。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            return;
        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }
}

進階:喚醒既有實例 可用 RegisterWindowMessage + PostMessage 或命名管道/TCP 通訊傳送參數給第一個實例。

技巧二:MDI 父視窗下單一子視窗實例管理

避免重複開啟相同功能視窗,提供「已開啟則聚焦」體驗。


public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
        this.IsMdiContainer = true;
    }

    private void OpenChildForm() where T : Form, new()
    {
        string formName = typeof(T).Name;
        
        // 尋找現有實例
        Form existing = this.MdiChildren.FirstOrDefault(f => f.Name == formName);
        if (existing != null)
        {
            existing.Activate(); // 聚焦現有視窗
            if (existing.WindowState == FormWindowState.Minimized)
                existing.WindowState = FormWindowState.Normal;
            return;
        }

        // 建立新實例
        var child = new T
        {
            MdiParent = this,
            Name = formName, // 重要:設定 Name 供後續查找
            Text = GetFriendlyName(formName)
        };
        child.FormClosed += (s, e) => { /* 可選:清理相關資源 */ };
        child.Show();
    }

    private string GetFriendlyName(string typeName) => typeName.Replace("Form", "").Replace("Child", "");

    // 使用範例
    private void menuCustomer_Click(object sender, EventArgs e) => OpenChildForm();
    private void menuOrder_Click(object sender, EventArgs e) => OpenChildForm();
}

技巧三:視窗淡入/淡出動畫 (Opacity 動畫)

使用 Timer 漸變 Opacity (0.0 ~ 1.0),適用於彈出式通知、工具提示視窗、啟動畫面。


public partial class FadeForm : Form
{
    private readonly System.Windows.Forms.Timer _fadeTimer = new() { Interval = 20 };
    private bool _fadeIn = true;
    private const double Step = 0.05; // 每步 5%

    public FadeForm()
    {
        InitializeComponent();
        this.Opacity = 0;
        this.ShowInTaskbar = false;
        _fadeTimer.Tick += FadeTimer_Tick;
    }

    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        _fadeIn = true;
        _fadeTimer.Start();
    }

    public void FadeOutAndClose()
    {
        _fadeIn = false;
        _fadeTimer.Start();
    }

    private void FadeTimer_Tick(object sender, EventArgs e)
    {
        if (_fadeIn)
        {
            this.Opacity = Math.Min(1.0, this.Opacity + Step);
            if (this.Opacity >= 1.0) _fadeTimer.Stop();
        }
        else
        {
            this.Opacity = Math.Max(0.0, this.Opacity - Step);
            if (this.Opacity <= 0.0)
            {
                _fadeTimer.Stop();
                this.Close();
            }
        }
    }

    // 使用範例:顯示 3 秒後自動淡出關閉
    public static void ShowToast(string message, int durationMs = 3000)
    {
        var toast = new FadeForm { Text = "通知" };
        var lbl = new Label { Text = message, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleCenter, Font = new Font("微軟正黑體", 12) };
        toast.Controls.Add(lbl);
        toast.Size = new Size(300, 100);
        toast.StartPosition = FormStartPosition.Manual;
        toast.Location = new Point(Screen.PrimaryScreen.WorkingArea.Width - toast.Width - 20,
                                   Screen.PrimaryScreen.WorkingArea.Height - toast.Height - 20);
        toast.Show();
        var timer = new System.Windows.Forms.Timer { Interval = durationMs };
        timer.Tick += (s, e) => { timer.Stop(); toast.FadeOutAndClose(); };
        timer.Start();
    }
}

技巧四:TextBox 按 Enter 鍵移動焦點 (模擬 Tab 鍵)

繼承 TextBox 建立可重複使用的自訂控制項,支援設計時屬性設定。


[ToolboxItem(true)]
public class EnterTabTextBox : TextBox
{
    private bool _enterAsTab = true;

    [DefaultValue(true), Category("Behavior")]
    public bool EnterAsTab
    {
        get => _enterAsTab;
        set => _enterAsTab = value;
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);
        if (_enterAsTab && e.KeyCode == Keys.Enter)
        {
            e.SuppressKeyPress = true; // 阻止 Enter 鍵預設行為 (如多行換行、按鈕 Click)
            this.SelectNextControl(this, true, true, true, true); // 移動到下一個 TabIndex 控制項
        }
    }

    // 多行模式下 Enter 應換行,不移動焦點
    protected override void OnKeyPress(KeyPressEventArgs e)
    {
        if (Multiline && e.KeyChar == '\r') return;
        base.OnKeyPress(e);
    }
}

用法:建置專案後,EnterTabTextBox 會出現在工具箱,拖曳使用即可。屬性視窗可切換 EnterAsTab。

技巧五:資料繫結格式化與驗證 (Binding + Format/Parse 事件)


private void BindWithFormat()
{
    var binding = new Binding("Text", dataSource, "Price", true, DataSourceUpdateMode.OnPropertyChanged);
    binding.Format += (s, e) => 
    {
        if (e.Value is decimal d) e.Value = d.ToString("N2"); // 顯示: 1,234.56
    };
    binding.Parse += (s, e) =>
    {
        if (decimal.TryParse(e.Value.ToString(), NumberStyles.Currency, CultureInfo.CurrentCulture, out var d))
            e.Value = d; // 輸入: 1,234.56 → decimal
        else
            e.Value = 0m;
    };
    txtPrice.DataBindings.Add(binding);
}

技巧六:非同步載入大量資料至 DataGridView (虛擬模式)


public class VirtualGridLoader
{
    private readonly DataGridView _grid;
    private readonly Func>> _pageLoader; // (pageIndex, pageSize) → data
    private const int PageSize = 1000;

    public VirtualGridLoader(DataGridView grid, Func>> pageLoader)
    {
        _grid = grid;
        _pageLoader = pageLoader;
        _grid.VirtualMode = true;
        _grid.CellValueNeeded += Grid_CellValueNeeded;
    }

    private async void Grid_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
    {
        int pageIndex = e.RowIndex / PageSize;
        int rowInPage = e.RowIndex % PageSize;
        
        // 簡化:實際應快取頁面資料
        var data = await _pageLoader(pageIndex, PageSize);
        if (rowInPage < data.Count)
            e.Value = data[rowInPage][e.ColumnIndex];
    }
}

技巧七:全域熱鍵註冊 (RegisterHotKey)


public class GlobalHotkey : IDisposable
{
    private readonly int _id;
    private readonly IntPtr _hWnd;
    private bool _disposed;

    public GlobalHotkey(IntPtr hWnd, int id, KeyModifiers modifiers, Keys key)
    {
        _hWnd = hWnd; _id = id;
        RegisterHotKey(_hWnd, _id, (uint)modifiers, (uint)key);
    }

    [DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
    [DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    public void Dispose()
    {
        if (!_disposed) { UnregisterHotKey(_hWnd, _id); _disposed = true; }
    }

    [Flags] public enum KeyModifiers { None = 0, Alt = 1, Control = 2, Shift = 4, Win = 8 }
}

// 使用
protected override void WndProc(ref Message m)
{
    const int WM_HOTKEY = 0x0312;
    if (m.Msg == WM_HOTKEY && m.WParam.ToInt32() == MY_HOTKEY_ID)
        OnGlobalHotkeyPressed();
    base.WndProc(ref m);
}

技巧八:拖放檔案至表單/控制項


private void Form_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
        e.Effect = DragDropEffects.Copy;
}

private void Form_DragDrop(object sender, DragEventArgs e)
{
    if (e.Data.GetData(DataFormats.FileDrop) is string[] files)
    {
        foreach (var file in files)
            ProcessFile(file); // 你的處理邏輯
    }
}
// 設計師設定 AllowDrop = true

技巧九:高 DPI 感知與縮放支援


// Program.cs (Main 前)
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); // .NET Core 3.0+ / .NET 5+

// App.manifest (專案屬性 → Application → Manifest)
/*

  
    True/PM
    PerMonitorV2
  

*/

技巧十:非同步等待對話框 (避免 UI 凍結)


private async void btnLongTask_Click(object sender, EventArgs e)
{
    using var dlg = new ProgressDialog("處理中...", async (progress, token) =>
    {
        for (int i = 0; i <= 100; i++)
        {
            await Task.Delay(50, token);
            progress.Report(i);
        }
    });
    dlg.ShowDialog(this);
}

// 簡易 ProgressDialog 實作略 (含 ProgressBar、Cancel 按鈕、IProgress 回報)

總結:開發檢核清單

  • [ ] 單一實例保護 (Mutex)
  • [ ] MDI 子視窗單一實例管理
  • [ ] 重要操作非同步 + 進度回報
  • [ ] 高 DPI 感知設定 (PerMonitorV2)
  • [ ] 自訂控制項封裝通用行為 (EnterTabTextBox 等)
  • [ ] 資源正確釋放 (IDisposable, using, Dispose 模式)
  • [ ] 例外處理與使用者友善錯誤訊息
  • [ ] 記錄關鍵操作日誌 (Log) 便於除錯
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved