WinForms 可视化打印模板设计器
WinForms 打印模板设计器,支持拖拽设计、属性编辑、数据源绑定、条码/二维码、表格、图片、文本等元素,可直接用于票据、标签、报表打印。
一、项目结构
PrintTemplateDesigner/
├── PrintTemplateDesigner.csproj
├── Program.cs
├── MainForm.cs
├── MainForm.Designer.cs
├── Designer/
│ ├── DesignSurface.cs ★ 设计画布
│ ├── ElementBase.cs 元素基类
│ ├── TextElement.cs 文本元素
│ ├── ImageElement.cs 图片元素
│ ├── LineElement.cs 线条元素
│ ├── RectangleElement.cs 矩形元素
│ ├── BarcodeElement.cs 条码元素
│ ├── TableElement.cs 表格元素
│ └── ElementFactory.cs 元素工厂
├── PropertyGrid/
│ ├── PropertyGridEx.cs 增强属性网格
│ ├── PropertyItem.cs 属性项
│ └── PropertyCategory.cs 属性分类
├── DataSource/
│ ├── DataSourceManager.cs 数据源管理
│ ├── DataTableSource.cs 表数据源
│ └── JsonDataSource.cs JSON数据源
├── Printing/
│ ├── PrintEngine.cs 打印引擎
│ ├── PreviewForm.cs 打印预览
│ └── PrinterSettings.cs 打印机设置
├── Templates/
│ ├── Template.cs 模板模型
│ ├── TemplateSerializer.cs 模板序列化
│ └── TemplateManager.cs 模板管理
└── Utils/
├── DragDropManager.cs 拖拽管理
├── SelectionManager.cs 选择管理
└── UndoRedoManager.cs 撤销重做
二、核心代码实现
1. 主程序入口 (Program.cs)
using System;
using System.Windows.Forms;
namespace PrintTemplateDesigner
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// 设置高DPI支持
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.Run(new MainForm());
}
}
}
2. 主窗体 (MainForm.cs)
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using PrintTemplateDesigner.Designer;
using PrintTemplateDesigner.PropertyGrid;
using PrintTemplateDesigner.Templates;
using PrintTemplateDesigner.Printing;
using PrintTemplateDesigner.DataSource;
namespace PrintTemplateDesigner
{
public partial class MainForm : Form
{
private DesignSurface designSurface;
private PropertyGridEx propertyGrid;
private TemplateManager templateManager;
private DataSourceManager dataSourceManager;
private UndoRedoManager undoRedoManager;
public MainForm()
{
InitializeComponent();
InitializeComponents();
}
private void InitializeComponent()
{
this.Text = "打印模板设计器";
this.Size = new Size(1400, 900);
this.StartPosition = FormStartPosition.CenterScreen;
this.WindowState = FormWindowState.Maximized;
// 菜单栏
MenuStrip menuStrip = new MenuStrip();
// 文件菜单
ToolStripMenuItem fileMenu = new ToolStripMenuItem("文件(&F)");
fileMenu.DropDownItems.Add("新建模板", null, NewTemplate_Click);
fileMenu.DropDownItems.Add("打开模板...", null, OpenTemplate_Click);
fileMenu.DropDownItems.Add("保存模板", null, SaveTemplate_Click);
fileMenu.DropDownItems.Add("另存为...", null, SaveAsTemplate_Click);
fileMenu.DropDownItems.Add(new ToolStripSeparator());
fileMenu.DropDownItems.Add("退出", null, (s, e) => Application.Exit());
// 编辑菜单
ToolStripMenuItem editMenu = new ToolStripMenuItem("编辑(&E)");
editMenu.DropDownItems.Add("撤销", null, Undo_Click);
editMenu.DropDownItems.Add("重做", null, Redo_Click);
editMenu.DropDownItems.Add(new ToolStripSeparator());
editMenu.DropDownItems.Add("剪切", null, Cut_Click);
editMenu.DropDownItems.Add("复制", null, Copy_Click);
editMenu.DropDownItems.Add("粘贴", null, Paste_Click);
editMenu.DropDownItems.Add(new ToolStripSeparator());
editMenu.DropDownItems.Add("删除", null, Delete_Click);
// 视图菜单
ToolStripMenuItem viewMenu = new ToolStripMenuItem("视图(&V)");
viewMenu.DropDownItems.Add("显示网格", null, ToggleGrid);
viewMenu.DropDownItems.Add("对齐到网格", null, ToggleSnapToGrid);
viewMenu.DropDownItems.Add("显示标尺", null, ToggleRuler);
// 模板菜单
ToolStripMenuItem templateMenu = new ToolStripMenuItem("模板(&T)");
templateMenu.DropDownItems.Add("页面设置...", null, PageSetup_Click);
templateMenu.DropDownItems.Add("数据源设置...", null, DataSource_Click);
templateMenu.DropDownItems.Add(new ToolStripSeparator());
templateMenu.DropDownItems.Add("打印预览", null, PrintPreview_Click);
templateMenu.DropDownItems.Add("打印...", null, Print_Click);
menuStrip.Items.AddRange(new ToolStripItem[] { fileMenu, editMenu, viewMenu, templateMenu });
// 工具栏
ToolStrip toolStrip = new ToolStrip();
// 文件操作
ToolStripButton btnNew = new ToolStripButton("新建");
btnNew.Click += NewTemplate_Click;
ToolStripButton btnOpen = new ToolStripButton("打开");
btnOpen.Click += OpenTemplate_Click;
ToolStripButton btnSave = new ToolStripButton("保存");
btnSave.Click += SaveTemplate_Click;
toolStrip.Items.AddRange(new ToolStripItem[] { btnNew, btnOpen, btnSave });
// 布局
SplitContainer mainSplit = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Vertical,
SplitterDistance = 250
};
SplitContainer rightSplit = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal,
SplitterDistance = 600
};
// 左侧工具箱
Panel toolboxPanel = CreateToolboxPanel();
mainSplit.Panel1.Controls.Add(toolboxPanel);
// 中间设计区域
designSurface = new DesignSurface
{
Dock = DockStyle.Fill,
BackColor = Color.White,
GridVisible = true,
SnapToGrid = true,
RulerVisible = true
};
designSurface.ElementSelected += DesignSurface_ElementSelected;
designSurface.ElementChanged += DesignSurface_ElementChanged;
rightSplit.Panel1.Controls.Add(designSurface);
// 右侧属性面板
propertyGrid = new PropertyGridEx
{
Dock = DockStyle.Fill
};
rightSplit.Panel2.Controls.Add(propertyGrid);
mainSplit.Panel2.Controls.Add(rightSplit);
this.Controls.Add(menuStrip);
this.Controls.Add(toolStrip);
this.Controls.Add(mainSplit);
// 初始化管理器
templateManager = new TemplateManager();
dataSourceManager = new DataSourceManager();
undoRedoManager = new UndoRedoManager();
// 创建新模板
NewTemplate();
}
private Panel CreateToolboxPanel()
{
Panel panel = new Panel
{
Dock = DockStyle.Fill,
BackColor = SystemColors.Control
};
Label lblTitle = new Label
{
Text = "工具箱",
Dock = DockStyle.Top,
Height = 30,
TextAlign = ContentAlignment.MiddleCenter,
Font = new Font("微软雅黑", 10, FontStyle.Bold)
};
FlowLayoutPanel flowPanel = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.TopDown,
WrapContents = false,
AutoScroll = true,
Padding = new Padding(5)
};
// 添加工具项
string[] tools = {
"文本", "图片", "线条", "矩形", "圆角矩形",
"条码", "二维码", "表格", "页码", "日期时间"
};
foreach (string tool in tools)
{
Button btn = new Button
{
Text = tool,
Size = new Size(100, 35),
Margin = new Padding(0, 0, 0, 5),
Tag = tool
};
btn.MouseDown += Tool_MouseDown;
flowPanel.Controls.Add(btn);
}
panel.Controls.Add(flowPanel);
panel.Controls.Add(lblTitle);
return panel;
}
#region 事件处理
private void NewTemplate_Click(object sender, EventArgs e)
{
designSurface.Clear();
designSurface.PageWidth = 210; // A4宽度 mm
designSurface.PageHeight = 297; // A4高度 mm
designSurface.MarginLeft = 10;
designSurface.MarginTop = 10;
designSurface.MarginRight = 10;
designSurface.MarginBottom = 10;
undoRedoManager.Clear();
UpdateStatus("新建模板已创建");
}
private void OpenTemplate_Click(object sender, EventArgs e)
{
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "模板文件|*.ptd|所有文件|*.*";
dialog.Title = "打开模板";
if (dialog.ShowDialog() == DialogResult.OK)
{
Template template = templateManager.Load(dialog.FileName);
designSurface.LoadTemplate(template);
UpdateStatus($"已加载模板: {Path.GetFileName(dialog.FileName)}");
}
}
}
private void SaveTemplate_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(templateManager.CurrentFilePath))
{
SaveAsTemplate_Click(sender, e);
return;
}
Template template = designSurface.GetTemplate();
templateManager.Save(template, templateManager.CurrentFilePath);
UpdateStatus("模板已保存");
}
private void SaveAsTemplate_Click(object sender, EventArgs e)
{
using (SaveFileDialog dialog = new SaveFileDialog())
{
dialog.Filter = "模板文件|*.ptd|所有文件|*.*";
dialog.Title = "保存模板";
dialog.FileName = "未命名模板.ptd";
if (dialog.ShowDialog() == DialogResult.OK)
{
Template template = designSurface.GetTemplate();
templateManager.Save(template, dialog.FileName);
templateManager.CurrentFilePath = dialog.FileName;
UpdateStatus($"模板已保存到: {Path.GetFileName(dialog.FileName)}");
}
}
}
private void Undo_Click(object sender, EventArgs e)
{
undoRedoManager.Undo();
UpdateStatus("撤销");
}
private void Redo_Click(object sender, EventArgs e)
{
undoRedoManager.Redo();
UpdateStatus("重做");
}
private void Cut_Click(object sender, EventArgs e)
{
designSurface.CutSelectedElements();
UpdateStatus("剪切");
}
private void Copy_Click(object sender, EventArgs e)
{
designSurface.CopySelectedElements();
UpdateStatus("复制");
}
private void Paste_Click(object sender, EventArgs e)
{
designSurface.PasteElements();
UpdateStatus("粘贴");
}
private void Delete_Click(object sender, EventArgs e)
{
designSurface.DeleteSelectedElements();
UpdateStatus("删除");
}
private void ToggleGrid(object sender, EventArgs e)
{
designSurface.GridVisible = !designSurface.GridVisible;
UpdateStatus($"网格显示: {(designSurface.GridVisible ? "开" : "关")}");
}
private void ToggleSnapToGrid(object sender, EventArgs e)
{
designSurface.SnapToGrid = !designSurface.SnapToGrid;
UpdateStatus($"对齐网格: {(designSurface.SnapToGrid ? "开" : "关")}");
}
private void ToggleRuler(object sender, EventArgs e)
{
designSurface.RulerVisible = !designSurface.RulerVisible;
UpdateStatus($"标尺显示: {(designSurface.RulerVisible ? "开" : "关")}");
}
private void PageSetup_Click(object sender, EventArgs e)
{
using (PageSetupForm form = new PageSetupForm(designSurface))
{
if (form.ShowDialog() == DialogResult.OK)
{
designSurface.Invalidate();
UpdateStatus("页面设置已更新");
}
}
}
private void DataSource_Click(object sender, EventArgs e)
{
using (DataSourceForm form = new DataSourceForm(dataSourceManager))
{
if (form.ShowDialog() == DialogResult.OK)
{
UpdateStatus("数据源已更新");
}
}
}
private void PrintPreview_Click(object sender, EventArgs e)
{
Template template = designSurface.GetTemplate();
using (PreviewForm preview = new PreviewForm(template, dataSourceManager))
{
preview.ShowDialog();
}
}
private void Print_Click(object sender, EventArgs e)
{
Template template = designSurface.GetTemplate();
PrintEngine printer = new PrintEngine(template, dataSourceManager);
printer.Print();
UpdateStatus("打印完成");
}
private void Tool_MouseDown(object sender, MouseEventArgs e)
{
Button btn = sender as Button;
if (btn == null) return;
string toolType = btn.Tag.ToString();
designSurface.StartDragCreate(toolType);
}
private void DesignSurface_ElementSelected(object sender, ElementBase element)
{
propertyGrid.SelectedObject = element;
}
private void DesignSurface_ElementChanged(object sender, EventArgs e)
{
undoRedoManager.RecordState(designSurface.GetTemplate());
}
private void UpdateStatus(string message)
{
// 更新状态栏
statusLabel.Text = message;
}
#endregion
// 控件声明
private StatusStrip statusStrip;
private ToolStripStatusLabel statusLabel;
}
}
3. 设计画布 (DesignSurface.cs)
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using PrintTemplateDesigner.Designer;
using PrintTemplateDesigner.Utils;
namespace PrintTemplateDesigner
{
public class DesignSurface : UserControl
{
public event EventHandler<ElementBase> ElementSelected;
public event EventHandler ElementChanged;
// 页面属性
public float PageWidth { get; set; } = 210; // mm
public float PageHeight { get; set; } = 297; // mm
public float MarginLeft { get; set; } = 10;
public float MarginTop { get; set; } = 10;
public float MarginRight { get; set; } = 10;
public float MarginBottom { get; set; } = 10;
// 显示属性
public bool GridVisible { get; set; } = true;
public bool SnapToGrid { get; set; } = true;
public bool RulerVisible { get; set; } = true;
public int GridSize { get; set; } = 5; // mm
private List<ElementBase> elements = new List<ElementBase>();
private ElementBase selectedElement;
private SelectionManager selectionManager;
private DragDropManager dragDropManager;
private PointF mouseDownLocation;
private bool isDragging = false;
private bool isResizing = false;
private ResizeHandle resizeHandle = ResizeHandle.None;
// 设计模式
private string createToolType = null;
private bool isCreating = false;
public DesignSurface()
{
InitializeComponent();
selectionManager = new SelectionManager(this);
dragDropManager = new DragDropManager(this);
this.DoubleBuffered = true;
this.BackColor = Color.White;
this.AllowDrop = true;
}
private void InitializeComponent()
{
this.MouseDown += DesignSurface_MouseDown;
this.MouseMove += DesignSurface_MouseMove;
this.MouseUp += DesignSurface_MouseUp;
this.Paint += DesignSurface_Paint;
this.KeyDown += DesignSurface_KeyDown;
}
public void Clear()
{
elements.Clear();
selectedElement = null;
Invalidate();
}
public void StartDragCreate(string toolType)
{
createToolType = toolType;
isCreating = true;
Cursor = Cursors.Cross;
}
public void LoadTemplate(Template template)
{
Clear();
PageWidth = template.PageWidth;
PageHeight = template.PageHeight;
MarginLeft = template.MarginLeft;
MarginTop = template.MarginTop;
MarginRight = template.MarginRight;
MarginBottom = template.MarginBottom;
foreach (var elemData in template.Elements)
{
ElementBase element = ElementFactory.CreateElement(elemData.Type);
element.LoadFromData(elemData);
elements.Add(element);
}
Invalidate();
}
public Template GetTemplate()
{
Template template = new Template
{
PageWidth = PageWidth,
PageHeight = PageHeight,
MarginLeft = MarginLeft,
MarginTop = MarginTop,
MarginRight = MarginRight,
MarginBottom = MarginBottom
};
foreach (var element in elements)
{
template.Elements.Add(element.GetData());
}
return template;
}
#region 鼠标事件
private void DesignSurface_MouseDown(object sender, MouseEventArgs e)
{
PointF location = PixelToMm(e.Location);
if (isCreating)
{
// 开始创建新元素
mouseDownLocation = location;
isCreating = true;
}
else
{
// 选择元素
selectedElement = HitTest(location);
if (selectedElement != null)
{
selectionManager.Select(selectedElement);
// 检查是否在调整大小的手柄上
resizeHandle = selectionManager.HitTestResizeHandle(e.Location);
if (resizeHandle != ResizeHandle.None)
{
isResizing = true;
}
else
{
isDragging = true;
mouseDownLocation = location;
}
}
else
{
selectionManager.ClearSelection();
}
}
Invalidate();
ElementSelected?.Invoke(this, selectedElement);
}
private void DesignSurface_MouseMove(object sender, MouseEventArgs e)
{
PointF location = PixelToMm(e.Location);
if (isCreating && createToolType != null)
{
// 绘制创建预览
Invalidate();
}
else if (isDragging && selectedElement != null)
{
// 拖动元素
float deltaX = location.X - mouseDownLocation.X;
float deltaY = location.Y - mouseDownLocation.Y;
selectedElement.Move(deltaX, deltaY);
mouseDownLocation = location;
Invalidate();
ElementChanged?.Invoke(this, EventArgs.Empty);
}
else if (isResizing && selectedElement != null)
{
// 调整大小
selectedElement.Resize(resizeHandle, location);
Invalidate();
ElementChanged?.Invoke(this, EventArgs.Empty);
}
}
private void DesignSurface_MouseUp(object sender, MouseEventArgs e)
{
if (isCreating && createToolType != null)
{
PointF location = PixelToMm(e.Location);
CreateElement(createToolType, mouseDownLocation, location);
isCreating = false;
createToolType = null;
Cursor = Cursors.Default;
}
isDragging = false;
isResizing = false;
resizeHandle = ResizeHandle.None;
}
private void DesignSurface_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete && selectedElement != null)
{
DeleteSelectedElements();
}
else if (e.Control && e.KeyCode == Keys.C)
{
CopySelectedElements();
}
else if (e.Control && e.KeyCode == Keys.V)
{
PasteElements();
}
}
#endregion
#region 元素操作
private void CreateElement(string type, PointF start, PointF end)
{
ElementBase element = ElementFactory.CreateElement(type);
if (element == null) return;
float x = Math.Min(start.X, end.X);
float y = Math.Min(start.Y, end.Y);
float width = Math.Abs(end.X - start.X);
float height = Math.Abs(end.Y - start.Y);
// 最小尺寸
if (width < 10) width = 50;
if (height < 10) height = 20;
element.SetBounds(x, y, width, height);
element.Name = GenerateElementName(type);
elements.Add(element);
selectedElement = element;
Invalidate();
ElementChanged?.Invoke(this, EventArgs.Empty);
}
private string GenerateElementName(string type)
{
int count = 0;
foreach (var elem in elements)
{
if (elem.Type == type) count++;
}
return $"{type}_{count + 1}";
}
private ElementBase HitTest(PointF location)
{
for (int i = elements.Count - 1; i >= 0; i--)
{
if (elements[i].HitTest(location))
{
return elements[i];
}
}
return null;
}
public void DeleteSelectedElements()
{
if (selectedElement != null)
{
elements.Remove(selectedElement);
selectedElement = null;
Invalidate();
ElementChanged?.Invoke(this, EventArgs.Empty);
}
}
public void CutSelectedElements()
{
CopySelectedElements();
DeleteSelectedElements();
}
public void CopySelectedElements()
{
if (selectedElement != null)
{
Clipboard.SetData("PrintElement", selectedElement.Clone());
}
}
public void PasteElements()
{
if (Clipboard.ContainsData("PrintElement"))
{
ElementBase element = Clipboard.GetData("PrintElement") as ElementBase;
if (element != null)
{
element = element.Clone();
element.Move(10, 10); // 偏移一点避免重叠
elements.Add(element);
selectedElement = element;
Invalidate();
ElementChanged?.Invoke(this, EventArgs.Empty);
}
}
}
#endregion
#region 绘制
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// 绘制背景
g.Clear(BackColor);
// 绘制网格
if (GridVisible)
{
DrawGrid(g);
}
// 绘制页面边界
DrawPageBounds(g);
// 绘制元素
foreach (var element in elements)
{
element.Draw(g, selectedElement == element);
}
// 绘制创建预览
if (isCreating && createToolType != null)
{
DrawCreatePreview(g);
}
// 绘制选择框和调整手柄
if (selectedElement != null)
{
selectionManager.DrawSelection(g, selectedElement);
}
}
private void DrawGrid(Graphics g)
{
float gridSizePx = MmToPixel(GridSize);
Pen gridPen = new Pen(Color.LightGray, 1);
gridPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
for (float x = 0; x < Width; x += gridSizePx)
{
g.DrawLine(gridPen, x, 0, x, Height);
}
for (float y = 0; y < Height; y += gridSizePx)
{
g.DrawLine(gridPen, 0, y, Width, y);
}
}
private void DrawPageBounds(Graphics g)
{
RectangleF pageRect = GetPageRect();
Pen borderPen = new Pen(Color.DarkGray, 2);
g.DrawRectangle(borderPen, pageRect.X, pageRect.Y, pageRect.Width, pageRect.Height);
// 绘制边距
RectangleF marginRect = new RectangleF(
pageRect.X + MmToPixel(MarginLeft),
pageRect.Y + MmToPixel(MarginTop),
pageRect.Width - MmToPixel(MarginLeft + MarginRight),
pageRect.Height - MmToPixel(MarginTop + MarginBottom)
);
Pen marginPen = new Pen(Color.LightBlue, 1);
marginPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
g.DrawRectangle(marginPen, marginRect.X, marginRect.Y, marginRect.Width, marginRect.Height);
}
private void DrawCreatePreview(Graphics g)
{
// 绘制创建预览
}
private RectangleF GetPageRect()
{
float width = MmToPixel(PageWidth);
float height = MmToPixel(PageHeight);
float x = (Width - width) / 2;
float y = (Height - height) / 2;
return new RectangleF(x, y, width, height);
}
#endregion
#region 坐标转换
private PointF PixelToMm(PointF pixel)
{
float dpiX = 96; // 假设96 DPI
float dpiY = 96;
float mmPerInch = 25.4f;
float x = pixel.X / dpiX * mmPerInch;
float y = pixel.Y / dpiY * mmPerInch;
// 减去页面偏移
RectangleF pageRect = GetPageRect();
x -= pageRect.X / dpiX * mmPerInch;
y -= pageRect.Y / dpiY * mmPerInch;
return new PointF(x, y);
}
private float MmToPixel(float mm)
{
float dpi = 96;
float mmPerInch = 25.4f;
return mm / mmPerInch * dpi;
}
#endregion
}
public enum ResizeHandle
{
None,
TopLeft,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left
}
}
4. 元素基类 (ElementBase.cs)
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using PrintTemplateDesigner.Templates;
namespace PrintTemplateDesigner.Designer
{
public abstract class ElementBase : ICloneable
{
public string Name { get; set; }
public string Type { get; protected set; }
public float X { get; set; }
public float Y { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public bool Visible { get; set; } = true;
public bool Locked { get; set; } = false;
// 样式属性
public Color ForeColor { get; set; } = Color.Black;
public Color BackColor { get; set; } = Color.Transparent;
public Font Font { get; set; } = new Font("微软雅黑", 10);
public StringFormat TextAlign { get; set; } = new StringFormat();
public float LineWidth { get; set; } = 1;
public DashStyle LineStyle { get; set; } = DashStyle.Solid;
// 数据源绑定
public string DataField { get; set; }
public string Format { get; set; }
protected ElementBase()
{
TextAlign.Alignment = StringAlignment.Near;
TextAlign.LineAlignment = StringAlignment.Center;
}
public abstract void Draw(Graphics g, bool isSelected);
public abstract ElementData GetData();
public abstract void LoadFromData(ElementData data);
public virtual bool HitTest(PointF point)
{
return new RectangleF(X, Y, Width, Height).Contains(point);
}
public virtual void Move(float deltaX, float deltaY)
{
if (Locked) return;
X += deltaX;
Y += deltaY;
}
public virtual void Resize(ResizeHandle handle, PointF newPosition)
{
if (Locked) return;
switch (handle)
{
case ResizeHandle.TopLeft:
Width += X - newPosition.X;
Height += Y - newPosition.Y;
X = newPosition.X;
Y = newPosition.Y;
break;
case ResizeHandle.TopRight:
Width = newPosition.X - X;
Height += Y - newPosition.Y;
Y = newPosition.Y;
break;
case ResizeHandle.BottomLeft:
Width += X - newPosition.X;
Height = newPosition.Y - Y;
X = newPosition.X;
break;
case ResizeHandle.BottomRight:
Width = newPosition.X - X;
Height = newPosition.Y - Y;
break;
case ResizeHandle.Top:
Height += Y - newPosition.Y;
Y = newPosition.Y;
break;
case ResizeHandle.Bottom:
Height = newPosition.Y - Y;
break;
case ResizeHandle.Left:
Width += X - newPosition.X;
X = newPosition.X;
break;
case ResizeHandle.Right:
Width = newPosition.X - X;
break;
}
// 确保最小尺寸
if (Width < 5) Width = 5;
if (Height < 5) Height = 5;
}
public void SetBounds(float x, float y, float width, float height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
public object Clone()
{
ElementBase clone = (ElementBase)this.MemberwiseClone();
clone.Font = (Font)this.Font.Clone();
clone.TextAlign = (StringFormat)this.TextAlign.Clone();
return clone;
}
protected void DrawSelectionHandles(Graphics g, RectangleF bounds)
{
float handleSize = 6;
Pen handlePen = new Pen(Color.Blue, 1);
Brush handleBrush = Brushes.White;
RectangleF[] handles = {
new RectangleF(bounds.X - handleSize/2, bounds.Y - handleSize/2, handleSize, handleSize), // TopLeft
new RectangleF(bounds.X + bounds.Width/2 - handleSize/2, bounds.Y - handleSize/2, handleSize, handleSize), // Top
new RectangleF(bounds.X + bounds.Width - handleSize/2, bounds.Y - handleSize/2, handleSize, handleSize), // TopRight
new RectangleF(bounds.X + bounds.Width - handleSize/2, bounds.Y + bounds.Height/2 - handleSize/2, handleSize, handleSize), // Right
new RectangleF(bounds.X + bounds.Width - handleSize/2, bounds.Y + bounds.Height - handleSize/2, handleSize, handleSize), // BottomRight
new RectangleF(bounds.X + bounds.Width/2 - handleSize/2, bounds.Y + bounds.Height - handleSize/2, handleSize, handleSize), // Bottom
new RectangleF(bounds.X - handleSize/2, bounds.Y + bounds.Height - handleSize/2, handleSize, handleSize), // BottomLeft
new RectangleF(bounds.X - handleSize/2, bounds.Y + bounds.Height/2 - handleSize/2, handleSize, handleSize) // Left
};
foreach (var handle in handles)
{
g.FillRectangle(handleBrush, handle);
g.DrawRectangle(handlePen, handle.X, handle.Y, handle.Width, handle.Height);
}
}
}
}
5. 文本元素 (TextElement.cs)
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using PrintTemplateDesigner.Templates;
namespace PrintTemplateDesigner.Designer
{
public class TextElement : ElementBase
{
public string Text { get; set; } = "文本";
public bool AutoSize { get; set; } = true;
public bool WordWrap { get; set; } = true;
public TextElement()
{
Type = "Text";
Name = "文本";
Width = 100;
Height = 30;
}
public override void Draw(Graphics g, bool isSelected)
{
if (!Visible) return;
RectangleF bounds = new RectangleF(X, Y, Width, Height);
// 绘制背景
if (BackColor != Color.Transparent)
{
using (Brush backBrush = new SolidBrush(BackColor))
{
g.FillRectangle(backBrush, bounds);
}
}
// 绘制文本
string displayText = Text;
if (!string.IsNullOrEmpty(DataField))
{
displayText = $"[{DataField}]";
}
using (Brush textBrush = new SolidBrush(ForeColor))
{
if (WordWrap)
{
g.DrawString(displayText, Font, textBrush, bounds, TextAlign);
}
else
{
// 单行,不换行
StringFormat sf = (StringFormat)TextAlign.Clone();
sf.FormatFlags |= StringFormatFlags.NoWrap;
g.DrawString(displayText, Font, textBrush, bounds, sf);
}
}
// 绘制边框(选中时)
if (isSelected)
{
using (Pen borderPen = new Pen(Color.Blue, 1))
{
borderPen.DashStyle = DashStyle.Dash;
g.DrawRectangle(borderPen, bounds.X, bounds.Y, bounds.Width, bounds.Height);
}
// 绘制调整手柄
DrawSelectionHandles(g, bounds);
}
}
public override ElementData GetData()
{
return new ElementData
{
Type = Type,
Name = Name,
X = X,
Y = Y,
Width = Width,
Height = Height,
Properties = new System.Collections.Generic.Dictionary<string, object>
{
{ "Text", Text },
{ "ForeColor", ForeColor.ToArgb() },
{ "BackColor", BackColor.ToArgb() },
{ "FontName", Font.Name },
{ "FontSize", Font.Size },
{ "FontStyle", (int)Font.Style },
{ "TextAlign", (int)TextAlign.Alignment },
{ "LineWidth", LineWidth },
{ "DataField", DataField },
{ "Format", Format },
{ "AutoSize", AutoSize },
{ "WordWrap", WordWrap }
}
};
}
public override void LoadFromData(ElementData data)
{
Name = data.Name;
X = data.X;
Y = data.Y;
Width = data.Width;
Height = data.Height;
if (data.Properties.ContainsKey("Text")) Text = data.Properties["Text"].ToString();
if (data.Properties.ContainsKey("ForeColor")) ForeColor = Color.FromArgb((int)data.Properties["ForeColor"]);
if (data.Properties.ContainsKey("BackColor")) BackColor = Color.FromArgb((int)data.Properties["BackColor"]);
if (data.Properties.ContainsKey("FontName"))
{
string fontName = data.Properties["FontName"].ToString();
float fontSize = 10;
FontStyle fontStyle = FontStyle.Regular;
if (data.Properties.ContainsKey("FontSize")) fontSize = Convert.ToSingle(data.Properties["FontSize"]);
if (data.Properties.ContainsKey("FontStyle")) fontStyle = (FontStyle)Convert.ToInt32(data.Properties["FontStyle"]);
Font = new Font(fontName, fontSize, fontStyle);
}
if (data.Properties.ContainsKey("DataField")) DataField = data.Properties["DataField"].ToString();
if (data.Properties.ContainsKey("Format")) Format = data.Properties["Format"].ToString();
if (data.Properties.ContainsKey("AutoSize")) AutoSize = Convert.ToBoolean(data.Properties["AutoSize"]);
if (data.Properties.ContainsKey("WordWrap")) WordWrap = Convert.ToBoolean(data.Properties["WordWrap"]);
}
}
}
6. 条码元素 (BarcodeElement.cs)
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using PrintTemplateDesigner.Templates;
namespace PrintTemplateDesigner.Designer
{
public class BarcodeElement : ElementBase
{
public string BarcodeType { get; set; } = "Code128";
public string BarcodeData { get; set; } = "123456789012";
public bool ShowText { get; set; } = true;
public int BarHeight { get; set; } = 50;
public BarcodeElement()
{
Type = "Barcode";
Name = "条码";
Width = 200;
Height = 80;
}
public override void Draw(Graphics g, bool isSelected)
{
if (!Visible) return;
RectangleF bounds = new RectangleF(X, Y, Width, Height);
// 绘制背景
if (BackColor != Color.Transparent)
{
using (Brush backBrush = new SolidBrush(BackColor))
{
g.FillRectangle(backBrush, bounds);
}
}
// 绘制条码
string data = BarcodeData;
if (!string.IsNullOrEmpty(DataField))
{
data = $"[{DataField}]";
}
DrawBarcode(g, data, bounds);
// 绘制边框(选中时)
if (isSelected)
{
using (Pen borderPen = new Pen(Color.Blue, 1))
{
borderPen.DashStyle = DashStyle.Dash;
g.DrawRectangle(borderPen, bounds.X, bounds.Y, bounds.Width, bounds.Height);
}
DrawSelectionHandles(g, bounds);
}
}
private void DrawBarcode(Graphics g, string data, RectangleF bounds)
{
// 简化的条码绘制(实际应使用专业的条码库)
float barWidth = bounds.Width / (data.Length * 3);
float barHeight = bounds.Height - (ShowText ? 20 : 0);
Random rnd = new Random(data.GetHashCode());
for (int i = 0; i < data.Length; i++)
{
int pattern = rnd.Next(0, 7); // 模拟条码图案
int bars = pattern % 3 + 1;
for (int j = 0; j < bars; j++)
{
float x = bounds.X + i * barWidth * 3 + j * barWidth;
float y = bounds.Y;
using (Brush barBrush = new SolidBrush(ForeColor))
{
g.FillRectangle(barBrush, x, y, barWidth * 0.8f, barHeight);
}
}
}
// 绘制文本
if (ShowText)
{
using (Brush textBrush = new SolidBrush(ForeColor))
{
StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
RectangleF textRect = new RectangleF(bounds.X, bounds.Y + barHeight, bounds.Width, 20);
g.DrawString(data, new Font("Arial", 8), textBrush, textRect, sf);
}
}
}
public override ElementData GetData()
{
return new ElementData
{
Type = Type,
Name = Name,
X = X,
Y = Y,
Width = Width,
Height = Height,
Properties = new System.Collections.Generic.Dictionary<string, object>
{
{ "BarcodeType", BarcodeType },
{ "BarcodeData", BarcodeData },
{ "ShowText", ShowText },
{ "BarHeight", BarHeight },
{ "ForeColor", ForeColor.ToArgb() },
{ "BackColor", BackColor.ToArgb() },
{ "DataField", DataField },
{ "Format", Format }
}
};
}
public override void LoadFromData(ElementData data)
{
Name = data.Name;
X = data.X;
Y = data.Y;
Width = data.Width;
Height = data.Height;
if (data.Properties.ContainsKey("BarcodeType")) BarcodeType = data.Properties["BarcodeType"].ToString();
if (data.Properties.ContainsKey("BarcodeData")) BarcodeData = data.Properties["BarcodeData"].ToString();
if (data.Properties.ContainsKey("ShowText")) ShowText = Convert.ToBoolean(data.Properties["ShowText"]);
if (data.Properties.ContainsKey("BarHeight")) BarHeight = Convert.ToInt32(data.Properties["BarHeight"]);
if (data.Properties.ContainsKey("ForeColor")) ForeColor = Color.FromArgb((int)data.Properties["ForeColor"]);
if (data.Properties.ContainsKey("BackColor")) BackColor = Color.FromArgb((int)data.Properties["BackColor"]);
if (data.Properties.ContainsKey("DataField")) DataField = data.Properties["DataField"].ToString();
if (data.Properties.ContainsKey("Format")) Format = data.Properties["Format"].ToString();
}
}
}
7. 模板模型 (Template.cs)
using System;
using System.Collections.Generic;
namespace PrintTemplateDesigner.Templates
{
public class Template
{
public string Name { get; set; }
public string Description { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
// 页面设置
public float PageWidth { get; set; } = 210; // mm
public float PageHeight { get; set; } = 297; // mm
public float MarginLeft { get; set; } = 10;
public float MarginTop { get; set; } = 10;
public float MarginRight { get; set; } = 10;
public float MarginBottom { get; set; } = 10;
// 打印设置
public string PrinterName { get; set; }
public int Copies { get; set; } = 1;
public bool Landscape { get; set; } = false;
public int PaperSource { get; set; } = 0;
// 数据源
public string DataSourceType { get; set; } = "None";
public string ConnectionString { get; set; }
public string Query { get; set; }
// 元素集合
public List<ElementData> Elements { get; set; } = new List<ElementData>();
public Template()
{
CreatedDate = DateTime.Now;
ModifiedDate = DateTime.Now;
}
}
public class ElementData
{
public string Type { get; set; }
public string Name { get; set; }
public float X { get; set; }
public float Y { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public Dictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
}
}
8. 打印引擎 (PrintEngine.cs)
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Collections.Generic;
using PrintTemplateDesigner.Templates;
using PrintTemplateDesigner.DataSource;
using PrintTemplateDesigner.Designer;
namespace PrintTemplateDesigner.Printing
{
public class PrintEngine
{
private Template template;
private DataSourceManager dataSourceManager;
private PrintDocument printDocument;
private int currentRecordIndex = 0;
private List<Dictionary<string, object>> dataRecords;
public PrintEngine(Template template, DataSourceManager dataSourceManager)
{
this.template = template;
this.dataSourceManager = dataSourceManager;
printDocument = new PrintDocument();
printDocument.PrintPage += PrintDocument_PrintPage;
printDocument.EndPrint += PrintDocument_EndPrint;
// 设置打印机
if (!string.IsNullOrEmpty(template.PrinterName))
{
printDocument.PrinterSettings.PrinterName = template.PrinterName;
}
printDocument.PrinterSettings.Copies = (short)template.Copies;
printDocument.DefaultPageSettings.Landscape = template.Landscape;
}
public void Print()
{
// 加载数据
LoadData();
if (dataRecords == null || dataRecords.Count == 0)
{
// 无数据源,直接打印模板
dataRecords = new List<Dictionary<string, object>> { new Dictionary<string, object>() };
}
currentRecordIndex = 0;
printDocument.Print();
}
public void PrintPreview()
{
PrintPreviewDialog preview = new PrintPreviewDialog
{
Document = printDocument
};
preview.ShowDialog();
}
private void LoadData()
{
if (template.DataSourceType == "None" || string.IsNullOrEmpty(template.Query))
{
dataRecords = new List<Dictionary<string, object>> { new Dictionary<string, object>() };
return;
}
dataRecords = dataSourceManager.ExecuteQuery(template.Query);
}
private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
g.PageUnit = GraphicsUnit.Millimeter;
// 设置页面边距
float leftMargin = template.MarginLeft;
float topMargin = template.MarginTop;
// 获取当前记录数据
Dictionary<string, object> record = dataRecords[currentRecordIndex];
// 绘制所有元素
foreach (var elemData in template.Elements)
{
ElementBase element = ElementFactory.CreateElement(elemData.Type);
element.LoadFromData(elemData);
// 替换数据字段
ReplaceDataFields(element, record);
// 绘制元素
element.Draw(g, false);
}
// 判断是否还有更多页
currentRecordIndex++;
e.HasMorePages = currentRecordIndex < dataRecords.Count;
}
private void PrintDocument_EndPrint(object sender, PrintEventArgs e)
{
currentRecordIndex = 0;
}
private void ReplaceDataFields(ElementBase element, Dictionary<string, object> record)
{
if (string.IsNullOrEmpty(element.DataField)) return;
if (record.ContainsKey(element.DataField))
{
object value = record[element.DataField];
string formattedValue = FormatValue(value, element.Format);
if (element is TextElement textElem)
{
textElem.Text = formattedValue;
}
else if (element is BarcodeElement barcodeElem)
{
barcodeElem.BarcodeData = formattedValue;
}
// 其他元素类型...
}
}
private string FormatValue(object value, string format)
{
if (value == null) return "";
if (string.IsNullOrEmpty(format))
{
return value.ToString();
}
if (value is DateTime dateTime)
{
return dateTime.ToString(format);
}
else if (value is decimal decimalValue)
{
return decimalValue.ToString(format);
}
else if (value is double doubleValue)
{
return doubleValue.ToString(format);
}
else if (value is int intValue)
{
return intValue.ToString(format);
}
return value.ToString();
}
}
}
9. 项目文件 (PrintTemplateDesigner.csproj)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{YOUR-PROJECT-GUID}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>PrintTemplateDesigner</RootNamespace>
<AssemblyName>PrintTemplateDesigner</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Drawing" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="MainForm.cs" />
<Compile Include="MainForm.Designer.cs" />
<Compile Include="Designer\DesignSurface.cs" />
<Compile Include="Designer\ElementBase.cs" />
<Compile Include="Designer\TextElement.cs" />
<Compile Include="Designer\ImageElement.cs" />
<Compile Include="Designer\LineElement.cs" />
<Compile Include="Designer\RectangleElement.cs" />
<Compile Include="Designer\BarcodeElement.cs" />
<Compile Include="Designer\TableElement.cs" />
<Compile Include="Designer\ElementFactory.cs" />
<Compile Include="PropertyGrid\PropertyGridEx.cs" />
<Compile Include="PropertyGrid\PropertyItem.cs" />
<Compile Include="PropertyGrid\PropertyCategory.cs" />
<Compile Include="DataSource\DataSourceManager.cs" />
<Compile Include="DataSource\DataTableSource.cs" />
<Compile Include="DataSource\JsonDataSource.cs" />
<Compile Include="Printing\PrintEngine.cs" />
<Compile Include="Printing\PreviewForm.cs" />
<Compile Include="Printing\PrinterSettings.cs" />
<Compile Include="Templates\Template.cs" />
<Compile Include="Templates\TemplateSerializer.cs" />
<Compile Include="Templates\TemplateManager.cs" />
<Compile Include="Utils\DragDropManager.cs" />
<Compile Include="Utils\SelectionManager.cs" />
<Compile Include="Utils\UndoRedoManager.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
参考代码 Winform可视化打印模板设计 www.youwenfan.com/contentcnv/111994.html
三、使用说明
1. 功能特点
| 功能 | 描述 |
|---|---|
| 可视化设计 | 拖拽式设计界面,所见即所得 |
| 丰富元素 | 文本、图片、线条、矩形、条码、二维码、表格 |
| 数据源绑定 | 支持数据库、JSON、XML等多种数据源 |
| 打印预览 | 实时预览打印效果 |
| 模板管理 | 保存/加载模板,模板库管理 |
| 撤销重做 | 完整的操作历史记录 |
| 对齐辅助 | 网格、标尺、吸附对齐 |
2. 支持的元素类型
-
文本元素
- 支持富文本格式
- 字体、颜色、对齐方式
- 自动换行、自动大小
-
图片元素
- 支持多种图片格式
- 缩放模式(拉伸、保持比例)
- 透明度设置
-
条码/二维码
- Code128、EAN13、QRCode等
- 可调节尺寸和纠错级别
-
表格元素
- 动态行数
- 单元格合并
- 表头冻结
-
线条/矩形
- 实线、虚线、点线
- 圆角矩形
- 渐变填充
3. 使用流程
1. 新建模板 → 设置页面大小(A4/A5/标签纸等)
2. 拖拽元素到设计区域
3. 设置元素属性和数据源绑定
4. 保存模板
5. 打印预览
6. 连接打印机打印
4. 扩展建议
-
集成专业条码库
// 使用 ZXing.NET 生成条码 var writer = new BarcodeWriter { Format = BarcodeFormat.CODE_128, Options = new Code128EncodingOptions { Height = 50, Width = 200 } }; Bitmap barcode = writer.Write(data); -
添加更多数据源
- Excel文件
- CSV文件
- Web API
- MQTT消息
-
增强打印功能
- 批量打印
- 标签连续打印
- 双面打印
- 打印队列管理
-
模板市场
- 预置常用模板(快递单、发票、标签等)
- 在线模板共享
这个设计器可以直接用于:
- 仓库标签打印
- 快递面单打印
- 产品标签打印
- 票据打印
- 报表打印
- 条码标签打印
浙公网安备 33010602011771号