图片插入功能,这个是Excel插件的一大刚需,但目前在VBA接口里开发,如果用Shapes.AddPicture方法插入的图片,没法对其添加事件,且图片插入后需等比例调整纵横比例特别麻烦,特别是对于插入的多个图片非统一的纵横尺寸比时。
例如一个很经典的需求是将插入的缩略图放大操作,没法点击、双击之类的事件响应对应的放大操作。
在VBA的方法中,还有一个方式,用窗体控件Image,可以实现图片插入后有事件关联。
窗体控件Image方法插入
但很遗憾的是,这个方法中插入的图片,当没有点选图片时,图片会有些模糊的情况。具体可对比下图左右部分。这个模糊的问题,还是老大难,难于达到完美。如果换成是VSTO的宿主项PictureBox,就完美解决了。
两种不同窗体控件方式插入图片
例如下图中的,用鼠标右键事件,调出上下文菜单。和Excel催化剂中的双击图片进行图片下载本地并调用Windows图片查看器实现图片的放大功能。
可以关联事件的插入图片方法
双击图片事件
保留有正确的原始图片纵横比例
笔者觉得这是个接近完美的解决方案(用窗体控件的方式插入图片,用户不能直接选择图片、移动图片,会有种奇怪的感觉)。
使用VSTO的PictureBox容器,将其图片属性设置成对应的图片对象即可,此处传入的图片对象是Image对象,不必像VBA方法里的只能传入文件全路径,可以方便某些数据库等方式存储二进制图片格式时,直接调用。
public static string AddPictureBoxToVstoWorkSheet(Excel.Range dstRange, Image img, string fileExt, bool hasBordersMargin, bool isLookupFromRangValue, int offsetRow, int offsetCol)
{
string picName = Guid.NewGuid().ToString();
Excel.Worksheet actSht = dstRange.Worksheet;
Worksheet vstoActSht = Globals.Factory.GetVstoObject(actSht);
PictureBox pictureBox = new PictureBox();
pictureBox.Image = img;
pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox.MouseDoubleClick += PictureBox_MouseDoubleClick;
AddContextMenuToPictureBox(pictureBox);
pictureBox.Name = picName;
ControlSite controlSite;
if (hasBordersMargin)
{
controlSite = vstoActSht.Controls.AddControl(
control: pictureBox,
top: dstRange.Top + 2,
left: dstRange.Left + 2,
height: dstRange.Height - 4,
width: dstRange.Width - 4,
name: picName);
}
else
{
controlSite = vstoActSht.Controls.AddControl(
control: pictureBox,
range: dstRange,
name: picName);
}
string srcFileName = string.Empty;
Excel.Range srcFileNameRange = null;
if (isLookupFromRangValue)
{
//取回图片文件名的单元格,可拿到文件名,我初始的单元格地址,为后面有可能插入过行、列来重新定位。
srcFileNameRange = dstRange.Offset[-offsetRow, -offsetCol];
srcFileName = srcFileNameRange == null ? "" : srcFileNameRange.Value2.ToString();
}
Common.vstoPicInfos.Add(
new Entity.VstoPicInfo()
{
PicName = picName,
IsLookupFromRangValue = isLookupFromRangValue,
PicOleName = controlSite.Name,
PicSht = actSht,
SrcFileNameRange = srcFileNameRange,
DstRange = dstRange,
SrcFileName = srcFileName,
ImageExt = fileExt,
PictureBoxImage = img
});
return picName;
}
其中核心代码有: 得到一个VSTO的WorkSheet对象和新建一个PictureBox对象。
Excel.Worksheet actSht = dstRange.Worksheet;
Worksheet vstoActSht = Globals.Factory.GetVstoObject(actSht);
PictureBox pictureBox = new PictureBox();
pictureBox.Image = img;
pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
通过方法vstoActSht.Controls.AddControl进行创建一个Picturebox到工作表中。具体各参数的意义和VBA的AddPicture方法类似,可自行查阅文档。
controlSite = vstoActSht.Controls.AddControl(
control: pictureBox,
top: dstRange.Top + 2,
left: dstRange.Left + 2,
height: dstRange.Height - 4,
width: dstRange.Width - 4,
name: picName);
通过以上的方法,即可创建了一个PictureBox对象容器,并且图片是我们传入的图片。这当中可以绑定一些PictureBox事件和上下文菜单等,如:
pictureBox.MouseDoubleClick += PictureBox_MouseDoubleClick;
private static void AddContextMenuToPictureBox(PictureBox pictureBox)
{
ContextMenuStrip contextMenuStrip = new ContextMenuStrip();
string[] btnsText = { "复制另存为", "移动至其他区域", "删除图片" };
foreach (var btnText in btnsText)
{
ToolStripButton toolStripButton = new ToolStripButton(btnText);
toolStripButton.Tag = pictureBox;
toolStripButton.Click += PictureBoxToolStripButton_Click;
contextMenuStrip.Items.Add(toolStripButton);
}
pictureBox.ContextMenuStrip = contextMenuStrip;
}
通过此方法插入的PictureBox,保存关闭后,Excel文件因无法在关闭状态下存储PictureBox对象,将会将其转换为OLE对象存储,设置过的事件将失效,Excel催化剂用了复杂的手段来恢复它,下篇其他技术时再进行介绍
图片插入这样一个刚需功能,在Excel催化剂上已经将其做到极致化的体验,也是有别于传统方式所实现的,在用户体验上,相信有对比后,也会喜欢上Excel催化剂这样的突破性的方式。
此篇已经对核心的技术及代码完成开源公开化,有兴趣的朋友们,可以一试。