我不知道你可以使用"Windows 7风格按钮"的解决方案.但是,我想要注意,编程不要求您只使用"开发人员"选项卡.换句话说:仅仅因为你想要一个按钮并不意味着你必须只使用Developer选项卡中的那个.实际上,Excel中几乎任何形状都可以分配一个宏.
获得类似于"Windows 7样式按钮"的按钮的最简单方法是从菜单中选择Insert一个Rectangle或一个.当您单击该形状然后在该形状的选项卡上选择一个预定义的灰度时,可以轻松实现"花式"灰色.这些按钮看起来与您想要的非常相似,通过右键单击这些形状可以轻松实现.Rounded RectangleShapesFormatshape styleassigned a macro
系好安全带,你要去兜风了。
首先,创建一个新的 C#(或 VB.NET .. 任何让你动摇的)类库,并添加一个新的 WPF UserControl,并设计你的 UI:
<UserControl x:Class="ComVisibleUI.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DataContext="ViewModel1"
d:DesignHeight="200" d:DesignWidth="300">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="32" />
</Grid.RowDefinitions>
<TextBlock Text="actual content here" Foreground="DimGray" HorizontalAlignment="Center" VerticalAlignment="Center" />
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="2">
<Button Width="128" Command="{Binding Command1}">
<TextBlock Text="Button1" />
</Button>
<Button Width="128" Command="{Binding Command2}">
<TextBlock Text="Button2" />
</Button>
</StackPanel>
</Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)
构建项目。
然后,添加一个新的 Form,停靠一个 WPF InteropElementHost控件,您应该能够将您的 WPF UserControl1(无论您怎么称呼它)添加为托管的 WPF 控件。
WPF 控件使用数据绑定来连接Command1和Command2(以及其他所有内容,实际上 - 阅读模型-视图-视图模型模式),因此您需要一个类来实现托管代码部分。如果您的逻辑全部是 VBA,那么这应该非常简单:
public class ViewModel1
{
public ViewModel1()
{
_command1 = new DelegateCommand(ExecuteCommand1);
_command2 = new DelegateCommand(ExecuteCommand2);
}
private readonly ICommand _command1;
public ICommand Command1 { get { return _command1; } }
public event EventHandler ExecutingCommand1;
private void ExecuteCommand1(object parameter)
{
ExecuteHandler(ExecutingCommand1);
}
private readonly ICommand _command2;
public ICommand Command2 { get { return _command2; } }
public event EventHandler ExecutingCommand2;
private void ExecuteCommand2(object parameter)
{
ExecuteHandler(ExecutingCommand2);
}
private void ExecuteHandler(EventHandler eventHandler)
{
var handler = eventHandler;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
}
Run Code Online (Sandbox Code Playgroud)
ADelegateCommand是一个非常好的小东西,遍布 Stack Overflow,所以如果您有任何问题,请随时搜索:
public class DelegateCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public DelegateCommand(Action<object> execute, Func<object,bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
}
Run Code Online (Sandbox Code Playgroud)
WinForms 表单将需要分配 WPF 控件的DataContext- 公开一个方法来做到这一点:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void SetDataContext(ViewModel1 viewModel)
{
hostedWPFControl.DataContext = viewModel;
}
}
Run Code Online (Sandbox Code Playgroud)
除此之外,这里不应该有任何代码。
WPF 喜欢 MVVM 模式,WinForms 喜欢 MVP(查找Model-View-Presenter)。WPF 部分托管在 WinForms 中,我们将创建一个演示者- 这是 VBA 代码将使用的对象:
[ComVisible(true)]
public interface IPresenter1
{
void Show();
}
Run Code Online (Sandbox Code Playgroud)
是的,那只是一个界面。等等,我们还需要一个:
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("18F3B8A8-EC60-4BCE-970A-6C0ABA145705")]
[ComVisible(true)]
public interface IPresenterEvents
{
void ExecuteCommand1(object message);
void ExecuteCommand2();
}
Run Code Online (Sandbox Code Playgroud)
该IPresenterEvents接口是您的“事件接收器”接口,VBA 代码需要实现该接口,但我会谈到它。首先我们需要实现实际的演示者:
public delegate void Command1Delegate(string message);
public delegate void Command2Delegate();
[ComSourceInterfaces(typeof(IPresenterEvents))]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[Guid("FAF36F86-7CB3-4E0C-A016-D8C84F6B07D7")]
public class Presenter1 : IPresenter1, IDisposable
{
private readonly Form _view;
public Presenter1()
{
var view = new Form1();
var viewModel = new ViewModel1();
viewModel.ExecutingCommand1 += viewModel_ExecutingCommand1;
viewModel.ExecutingCommand2 += viewModel_ExecutingCommand2;
view.SetDataContext(viewModel);
_view = view;
}
public event Command1Delegate ExecuteCommand1;
private void viewModel_ExecutingCommand1(object sender, EventArgs e)
{
var handler = ExecuteCommand1;
if (handler != null)
{
handler.Invoke("Hello from Command1!");
}
}
public event Command2Delegate ExecuteCommand2;
private void viewModel_ExecutingCommand2(object sender, EventArgs e)
{
var handler = ExecuteCommand2;
if (handler != null)
{
handler.Invoke();
}
}
public void Show()
{
_view.ShowDialog();
}
public void Dispose()
{
_view.Dispose();
}
}
Run Code Online (Sandbox Code Playgroud)
现在,转到项目的属性,并选中“注册 COM 互操作”复选框,然后构建项目;在[调试]选项卡中,选择启动操作“启动外部程序”,然后在您的机器上找到EXCEL.EXE可执行文件:当您按F5时,Visual Studio将启动带有调试器的Excel,然后您可以打开VBE (Alt+F11),添加对您刚刚构建的 .tlb(类型库)的引用(您将在 .net 项目目录中的 下找到它\bin\debug\theprojectname.tlb,假设是调试版本),然后就可以了。
这里有很多问题,我稍后会回来修复:
Dispose()方法没有公开,也不会在任何时候显式或隐式调用,这是......脏。无论如何,我已将此代码添加到ThisWorkbook:
Option Explicit
Private WithEvents view As ComVisibleUI.Presenter1
Public Sub DoSomething()
Set view = New ComVisibleUI.Presenter1
view.Show
End Sub
Private Sub view_ExecuteCommand1(ByVal message As Variant)
MsgBox message
End Sub
Private Sub view_ExecuteCommand2()
MsgBox "Hello from WPF!"
End Sub
Run Code Online (Sandbox Code Playgroud)
当我ThisWorkbook.DoSomething从直接窗口(Ctrl + G)运行时,我得到了这个:
理论上(至少根据MSDN),这就是您需要做的全部。正如我所说,这些事件处理程序由于某种原因没有被调用,但是,嘿,你得到了闪亮的按钮!...以及 WPF 的所有功能来设计您的 UI :)