VBA Windows 7样式button

我很确定这个问题已经在networking上问了很多,我已经在几个论坛上看到了很多的问题和答案,但是我从来没有看到明确的答案,所以我想知道:

是可以的,使用Windows 7风格的button

在这里输入图像说明

在Excel VBA或我必须使用这些灰色的东西看起来像他们来自

在这里输入图像说明

我不想使用图像,我的意思是导入这些“ActiveX控件”,我认为这是他们的名字。

扣上去,你在一趟。


首先,创build一个新的C#(或VB.NET ..不pipe你的船是什么)类库,并添加一个新的WPF UserControl,并devise你的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> 

build立这个项目。

然后,添加一个新的窗体,停靠一个WPF Interop ElementHost控件,并且您应该能够添加您的WPF UserControl1 (无论您调用它)作为托pipe的WPF控件。

WPF控件使用数据绑定来连接Command1Command2 (以及其他一切,真的,在Model-View-ViewModel模式中),所以你需要一个类来实现托pipe代码部分。 如果你的逻辑是所有的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); } } } 

一个DelegateCommand是一个非常好的小东西,都堆栈溢出,所以不要犹豫,search如果您有任何问题:

 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); } } 

WinForms窗体将需要分配WPF控件的DataContext – 公开一个方法来做到这一点:

 public partial class Form1 : Form { public Form1() { InitializeComponent(); } public void SetDataContext(ViewModel1 viewModel) { hostedWPFControl.DataContext = viewModel; } } 

除此之外, 这里不应该有任何代码


WPF喜欢MVVM模式,WinForms喜欢MVP(lookup Model-View-Presenter )。 在WinForms中托pipe的WPF部分,我们将做一个演示者 – 这是VBA代码将使用的对象:

 [ComVisible(true)] public interface IPresenter1 { void Show(); } 

是的,这只是一个界面 。 等等,我们需要另一个:

 [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] [Guid("18F3B8A8-EC60-4BCE-970A-6C0ABA145705")] [ComVisible(true)] public interface IPresenterEvents { void ExecuteCommand1(object message); void ExecuteCommand2(); } 

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(); } } 

现在,转到项目的属性,并检查“注册COM互操作”checkbox,然后build立项目; 在[debugging]选项卡中,select启动操作 “启动外部程序”,并在您的机器上findEXCEL.EXE可执行文件:当您按F5时,Visual Studio将启动带有debugging器的Excel,然后您可以打开VBE (Alt + F11),添加一个对你刚刚build立的.tlb(types库)的引用(你可以在你的.net项目目录下的\bin\debug\theprojectname.tlbfind它)而且应该这样做。

这里有很多问题,我稍后会回来修复:

  • Dispose()方法没有公开,并且不会在任何时候显式或隐式地调用,这是…脏的。
  • 尽pipe从C#debugging器的angular度来看,一切似乎都在进行,但是我无法得到运行所需的VBA处理程序。 如果您打算在VBA中实现逻辑,那么这可能是一个大问题,而不仅仅是调用UI。 OTOH你可以访问.net代码,也可以在C#/ VB.NET中实现演示者本身的演示者逻辑,然后你不需要让这些事件处理函数工作。

无论如何,我已经将此代码添加到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 

而当我从即时窗口 (Ctrl + G)运行ThisWorkbook.DoSomething ,我得到这个:

带有闪亮命令按钮的WPF驱动的用户界面

理论上(至less根据MSDN ),这就是你所需要做的。 正如我所说的,这些事件处理程序不会因为某种原因而被调用,但是,嘿,你会得到你shiny的button! …和WPF现在devise你的用户界面的所有权力:)

我不知道可以使用“Windows 7样式button”的解决scheme。 然而,我想指出的是,编程不要求你只使用“开发者”选项卡。 换句话说:仅仅因为你想要一个button并不意味着你只能使用Developer标签中的那个button。 实际上,Excel中的几乎任何形状都可以被分配一个macros。

获得类似于“Windows 7样式button”的button最简单的方法是从“ Shapes菜单中Insert RectangleRounded Rectangle 。 当您单击该形状,然后在该形状的“ Format选项卡上select一种预定义的灰度shape style时,可以轻松实现“奇特”的灰色着色。 这些button看起来与您想要的非常相似,并且可以通过右键单击这些形状来轻松assigned a macro

将使用Excel形状创建的按钮与标准Windows 7按钮进行比较