VBA Windows 7个样式按钮

VBA Windows 7 style buttons

我很确定这个问题已经在网络上被问了很多,我已经在几个论坛上阅读了很多问题和他们的 "answers" 但我从来没有看到一个明确的答案所以我想知道:

有可能,使用Windows 7 种风格的按钮

在 Excel VBA 还是我必须使用这些看起来像是来自

的灰色东西

?

不想 使用图像,我的意思是导入这些 "ActiveX Controls",我想那是他们的名字。

我不知道可以使用 "Windows 7 style buttons" 的解决方案。然而,我想指出,编程并不要求您专门使用 "Developer" 选项卡。换句话说:仅仅因为您想要一个按钮并不意味着您必须使用“开发人员”选项卡中的按钮。事实上,几乎 Excel 内的任何形状都可以指定一个宏。

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

系好安全带,你要上路了。


首先,创建一个新的 C#(或 VB.NET.. 随心所欲)class 库,并添加一个新的 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>

构建项目。

然后,添加一个新窗体,停靠一个 WPF Interop ElementHost 控件,您应该能够添加 WPF UserControl1(无论您如何称呼它)作为托管 WPF 控件。

WPF 控件使用数据绑定来连接 Command1Command2(以及其他一切,真的 - 阅读 Model-View-ViewModel 模式),因此您需要 class 来实现 托管代码 部分。如果你的逻辑全部是 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 是一个非常好的小东西,在 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);
    }
}

WinForms 表单将需要分配 WPF 控件的 DataContext - 公开一个方法来执行此操作:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    public void SetDataContext(ViewModel1 viewModel)
    {
        hostedWPFControl.DataContext = viewModel;
    }
}

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


WPF 喜欢 MVVM 模式,WinForms 喜欢 MVP(查找 Model-View-Presenter)。 WPF 部分托管在 WinForms 中,我们将制作一个 presenter - 这是 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 接口是您的 "event sink" 接口,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();
    }
}

现在,转到项目的属性,选中 "Register for COM interop" 复选框,然后构建项目;在 [Debug] 选项卡中,select start action "Start external program",然后在您的计算机上找到 EXCEL.EXE 可执行文件:当您按 F5 时,Visual Studio 将在附加调试器的情况下启动 Excel,然后您可以打开 VBE (Alt+F11),添加对您刚刚构建的 .tlb(类型库)的引用(您会发现它在您的 .net 项目目录中,在 \bin\debug\theprojectname.tlb 下,假设是调试版本),这应该可以做到。

这里有很多问题,我稍后会回来解决:

  • Dispose() 方法未公开,并且不会在任何时候显式或隐式调用,这是...脏的。
  • 虽然从 C# 调试器的角度来看一切似乎都在工作,但我无法将 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

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

理论上(至少根据 MSDN),这就是您需要做的全部。正如我所说,这些事件处理程序由于某种原因没有被调用,但是嘿,你得到了闪亮的按钮! ...以及 WPF 的所有功能来设计您的 UI 现在 :)