在企業級 WinForms 開發中,專業的圖片瀏覽控件是剛需。本文分享一個開源級 UcImageView 控件的完整實現,涵蓋鼠標滾輪縮放、拖拽漫游、90°旋轉、適應大小、打印輸出等核心功能,代碼結構清晰,可直接集成或二次開發。
控件繼承 UserControl,核心組件:
PictureBox (picView):承載圖像Panel (PnlMain):可視區域容器hScrollBarImageView, vScrollBarImageView):大圖導航
關鍵邏輯在 zoom(Point center, int zoomIndexBy1000):
private void zoom(Point center, int zoomIndexBy1000) {
Point oldSize = new Point(picView.Width, picView.Height);
// 以 X 方向為基準縮放,Y 方向按比例計算,避免累積誤差
picView.Width = picView.Width * zoomIndexBy1000 / 1000;
picView.Height = picView.Width * imgIndexBy1000 / 1000;
// 重新定位:保持滾輪位置對應的圖像點不變
picView.Left -= ((picView.Width - oldSize.X) * (center.X * 1000 / oldSize.X)) / 1000;
picView.Top -= ((picView.Height - oldSize.Y) * (center.Y * 1000 / oldSize.Y)) / 1000;
UpdateScrollBars(); // 同步滾動條可見性、最大值、Value
}
滾輪事件處理 (picView_MouseWheel):
private void picView_MouseWheel(object sender, MouseEventArgs e) {
if (Control.ModifierKeys == Keys.Control) { // Ctrl+滾輪 = 縮放
if (e.Delta > 0 && picView.Width < 10000) zoom(e.Location, 1100); // 放大 10%
else if (e.Delta < 0 && picView.Image.Width / picView.Width < 5) zoom(e.Location, 900); // 縮小 10%
CenterImage();
} else if (Control.ModifierKeys == Keys.Shift) { // Shift+滾輪 = 水平滾動
if (hScrollBarImageView.Visible) AdjustScroll(hScrollBarImageView, e.Delta);
} else { // 普通滾輪 = 垂直滾動
if (vScrollBarImageView.Visible) AdjustScroll(vScrollBarImageView, e.Delta);
}
}
鼠標按下記錄起始點,移動時計算偏移並同步滾動條:
private void picView_MouseMove(object sender, MouseEventArgs e) {
if (!mnuMoveChecked || !_isMouseDown) return;
int dx = _startPoint.X - e.X;
int dy = _startPoint.Y - e.Y;
int newX = -picView.Left + dx;
int newY = -picView.Top + dy;
// 邊界檢查後更新滾動條 Value,觸發 ValueChanged 事件移動圖片
if (hScrollBarImageView.Visible) hScrollBarImageView.Value = Clamp(newX, 0, hScrollBarImageView.Maximum);
else picView.Left = -newX;
if (vScrollBarImageView.Visible) vScrollBarImageView.Value = Clamp(newY, 0, vScrollBarImageView.Maximum);
else picView.Top = -newY;
}
PnlMain_Resize 確保縮放/最大化視窗時,圖片相對中心點位置不變,滾動條同步更新。
public void LeftRotateImage() {
if (picView.Image != null) {
picView.Image.RotateFlip(RotateFlipType.Rotate90FlipX);
picView.Refresh();
CenterImage();
}
}
public void FitImageSize() {
if (picView.Image == null) return;
float ratioX = (float)PnlMain.Width / picView.Width;
float ratioY = (float)PnlMain.Height / picView.Height;
float ratio = Math.Min(ratioX, ratioY);
picView.Scale(ratio, ratio);
CenterImage();
}
public void AddImage(string fileName, bool isAsync) {
if (isAsync) {
picView.LoadCompleted += (s, e) => picView.UseWaitCursor = false;
picView.UseWaitCursor = true;
picView.WaitOnLoad = false;
picView.LoadAsync(fileName);
} else {
picView.Image = Image.FromFile(fileName);
}
InitialImage(); // 重置縮放、滾動條、居中
}
private void mnuPrint_Click(object sender, EventArgs e) {
if (picView.Image == null) return;
using (var dlg = new PrintDialog { Document = _printDoc }) {
if (dlg.ShowDialog() == DialogResult.OK) {
_printDoc.Print();
}
}
}
private void printDoc_PrintPage(object sender, PrintPageEventArgs e) {
FitImageSize(); // 打印按適應大小
e.Graphics.DrawImage(picView.Image, picView.Location.X, picView.Location.Y, picView.Width, picView.Height);
}
public partial class FrmImageViewer : Form {
public FrmImageViewer() { InitializeComponent(); }
private void FrmImageViewer_Shown(object sender, EventArgs e) {
ucImageView.AddImage(Image.FromFile(Application.StartupPath + @"\Windows7.jpg"));
}
private void btnOpen_Click(object sender, EventArgs e) {
if (openFileDialog.ShowDialog() == DialogResult.OK)
ucImageView.AddImage(Image.FromFile(openFileDialog.FileName));
}
private void btnZoomIn_Click(object sender, EventArgs e) => ucImageView.ZoomInImage();
private void btnZoomOut_Click(object sender, EventArgs e) => ucImageView.ZoomOutImage();
private void btnFitSize_Click(object sender, EventArgs e) => ucImageView.FitImageSize();
private void btnRotateLeft_Click(object sender, EventArgs e) => ucImageView.LeftRotateImage();
private void btnRotateRight_Click(object sender, EventArgs e) => ucImageView.RightRotateImage();
private void btnPan_Click(object sender, EventArgs e) {
btnPan.Checked = ucImageView.MoveImageChecked = !btnPan.Checked;
ucImageView.MoveImage();
}
// 同步控件內部菜單狀態
private void ucImageView_OnMoveImageCheckedChanged(object sender, EventArgs e)
=> btnPan.Checked = ucImageView.MoveImageChecked;
}
System.Drawing.Imaging 幀選擇WriteableBitmap 實現硬件加速渲染這個 UcImageView 控件雖不及 ACDSee 專業,但作為企業級應用的嵌入式圖片查看器,功能完整、代碼開源、易於定製。核心在於座標變換數學(縮放中心點不變、滾動條同步、容器尺寸變化補償)的精確把控。歡迎 Fork 擴展,共同打磨更強大的組件庫。