使用 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 通訊傳送參數給第一個實例。
避免重複開啟相同功能視窗,提供「已開啟則聚焦」體驗。
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();
}
使用 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 建立可重複使用的自訂控制項,支援設計時屬性設定。
[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。
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);
}
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];
}
}
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
// Program.cs (Main 前)
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); // .NET Core 3.0+ / .NET 5+
// App.manifest (專案屬性 → Application → Manifest)
/*
True/PM
PerMonitorV2
*/
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 回報)
IDisposable, using, Dispose 模式)