65.9K
CodeProject 正在变化。 阅读更多。
Home

RefEdit .NET 模拟

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (13投票s)

2009 年 1 月 29 日

CPOL

6分钟阅读

viewsIcon

97010

downloadIcon

3931

.NET 的 RefEdit 控件的简单实现

引言   

最初发布这篇文章时,它只是一个简单的概念验证,展示了可以创建一个 RefEdit 控件来与 Excel 和 .NET 应用程序进行通信。

为了公平起见,我想说,控件中的所有想法并非都来自我。我在这里(MSDN 博客)找到了一些有用的信息。尽管它有效,但我无法在 Excel 2003 中使其正常工作。仅仅更改 PIA 引用是不够的。它通过 RibbionUI 与 Office 2007 绑定。我花了一些时间为 Excel 2003 构建了一个控件,并在此提供。

在整合我的工作并使用 MSDN 博客的一些技术后,我开发了这个与 Excel 2003 兼容的用户控件。

RefEditControl/Example_1.png

文章更新

过了很久,我终于有机会回到这个组件。我阅读了下面的评论,并决定更新组件以支持 Office 2007(Office 2010 将是下一个)。最新的控件包含许多修复和增强功能。

尽管我仍然提供 Excel 2003 组件,但本文反映了 Excel 2007 组件中存在的更改。

注意: Excel 2003 组件不再更新,并且已停产。

已升级到 .NET 4.0。

背景 

几年前,我开始了一个项目,要求我的 .NET 应用程序与 Excel 进行通信。该项目要求用户能够从电子表格中选择要上传到 SQL 的列。

正如所有进行过 Excel VBA 编程的人都知道的那样,RefEdit 控件非常棒!它允许用户通过鼠标或键盘选择电子表格中的范围。

随着 .NET 和 VSTO 的推出,Microsoft 没有发布 RefEdit 控件。这意味着将您喜欢的、使用 RefEdit 控件的 VBA 代码移植到 .NET 将不会那么顺利。

和大家一样,我立刻打开了 Google 搜索引擎,搜索了 .NET 的 RefEdit 控件。猜猜结果如何?从未找到,并且我访问的所有论坛都说 Microsoft 不会提供该控件,因为 RefEdit 控件与 Excel 紧密集成。我找到的所有示例都说要使用 InputBox。虽然这是一个变通方法,但不应是解决方案。

控件的布局和依赖项

我决定使用用户控件作为组件的基础。RefEdit 控件是使用以下组件构建的:

  • 文本框
  • 按钮

该控件依赖于 Microsoft.Office.Interop.Excel DLL。下载基于您机器上安装的 Office 版本的组件非常重要。当前可供下载的版本如下:

  • Office 2003
  • Office 2007

使用控件

现在我们已经完成了所有正式的对话和警告,让我们将注意力转移到文章的有趣部分:代码!

配置控件

将控件添加到工具箱后。只需将其拖放到窗体上的任何位置即可。

此时,您必须将控件“连接”到将要“通信”的 Excel 电子表格。该控件提供一个名为“ExcelConnector”的属性,该属性必须设置正确,控件才能正常工作。

Me.Excel2007RefEdit1.ExcelConnector = xl   

RefEdit 控件的连接属性是 Excel.Application 类型。这意味着您必须在窗体上实例化 Excel 对象。

Dim xl As Microsoft.Office.Interop.Excel.Application

 Private Sub Form2_Shown(ByVal sender As Object,
     ByVal e As System.EventArgs) Handles Me.Shown

        xl = New Microsoft.Office.Interop.Excel.Application
        xl.Workbooks.Add()
        xl.Visible = True

        Me.Excel2007RefEdit1.ExcelConnector = xl

 End Sub

此时,这就是控件正常工作所需的所有配置。其余的操作将在控件内部完成。

它如何捕获单元格选择?

当用户选择 RefEdit 控件的文本框时,我通过 AddHandler 方法捕获 Excel 工作表的 SelectionChange 事件。

Private Sub txtAddress_Enter(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtAddress.Enter
        If ExcelConnector Is Nothing Then Return

        If Me.Address <> String.Empty Then xlSheet.Range(Me.Address).Select()
        AddHandler xlSheet.SelectionChange, AddressOf SelectionChange

End Sub 

捕获 Excel 范围的 SelectionChange 方法会将值写入文本框。由于跨线程问题,它需要使用委托。

Private Delegate Sub WriteValue(ByVal value As String)

Private Sub SelectionChange(ByVal target As Excel.Range)
    Call WriteData("'" & target.Worksheet.Name & "'!" & target.Address)
    Call NAR(target)
End Sub

Private Sub WriteData(ByVal value As String)
    If Me.InvokeRequired Then
        Me.Invoke(New WriteValue(AddressOf WriteData), New Object() {value})
    Else
        Me.txtAddress.Text = value
        Me.Address = value
        RaiseEvent Changed(Me, New System.EventArgs())
    End If
End Sub 

数据写入后,我会确保销毁 Excel 引用。这是使用 NAR 方法完成的。

Private Sub NAR(ByVal ComObj As Object)
    Try
        System.Runtime.InteropServices.Marshal.ReleaseComObject(ComObj)
        ComObj = Nothing

        GC.Collect()
        GC.WaitForPendingFinalizers()
    Catch ex As Runtime.InteropServices.COMException
        MessageBox.Show(ex.Message)
    End Try
End Sub 

当用户点击 RefEdit 控件外部时,控件会释放对 Excel 事件的句柄。

Private Sub txtAddress_Leave(ByVal sender As Object,
    If ExcelConnector Is Nothing Then Return
    RemoveHandler xlSheet.SelectionChange, EventDel_SelectionChange
End Sub 

AddHandler 和 RemoveHandler 在文本框的 Enter 和 Leave 事件中处理,以适应窗体上多个控件的使用。

如何最小化/最大化控件?

RefEditControl/Example_2.png 

要最小化或最大化控件,用户可以单击 RefEdit 控件按钮。如上所示,_Resize 方法会将用户窗体折叠为仅显示 RefEdit 控件。

Private Sub _Resize()

   ' Manages the BeforeResize Event
   Dim args As New EventArgs.BeforeResizeEventArgs With {.DisplayState = Me.DisplayState}
   Call OnBeforeResize(args)
If args.Cancel Then Return

   For Each c As Control In ParentForm.Controls
       If Not TypeOf c Is Excel2007RefEdit Then
           c.Visible = DisplayState.IsParentMinimized
       End If
   Next

   Me.Visible = True

   If DisplayState.ActualParent IsNot Nothing Then
       Me.ParentForm.Controls.Remove(Me.MemberwiseClone)
       DisplayState.ActualParent.Controls.Add(Me)
       DisplayState.ActualParent = Nothing
   Else
       If Not TypeOf Me.Parent Is Form Then
           DisplayState.ActualParent = Me.Parent
           Me.ParentForm.Controls.Add(Me)
       End If
   End If

   If Not DisplayState.IsParentMinimized Then
       Me.btnState.Image = My.Resources.RefEdit1
       DisplayState.ParentClientSize = ParentForm.ClientSize
       DisplayState.ControlPrevX = Left
       DisplayState.ControlPrevY = Top
       DisplayState.ControlAnchor = Anchor

       Anchor = AnchorStyles.Left
       ParentForm.ClientSize = New Size(Me.Width, Me.Height)
       Left = 0
       Top = 0

       DisplayState.ParentPrevBorder = ParentForm.FormBorderStyle
       ParentForm.FormBorderStyle = FormBorderStyle.FixedDialog
       DisplayState.ShowParentControlBox = ParentForm.ControlBox
       ParentForm.ControlBox = False
   Else
       Me.btnState.Image = My.Resources.RefEdit0
       ParentForm.ClientSize = DisplayState.ParentClientSize
       Anchor = DisplayState.ControlAnchor
       Left = DisplayState.ControlPrevX
       Top = DisplayState.ControlPrevY
       ParentForm.FormBorderStyle = DisplayState.ParentPrevBorder
       ParentForm.ControlBox = DisplayState.ShowParentControlBox
   End If

   DisplayState.IsParentMinimized = Not DisplayState.IsParentMinimized

   ' Raises the AfterResize event
   RaiseEvent AfterResize(Me, New EventArgs.AfterResizeEventArgs With {.DisplayState = DisplayState})

End Sub 

也可以通过键盘快捷键 (F4) 调整控件的大小。此键盘快捷键的一个优点是 Excel refedit 控件不具备,因为 (F4) 快捷键可以在两个方向上调整大小。Excel refedit 仅缩小控件。这将缩小和增长。

Private Sub txtAddress_PreviewKeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.PreviewKeyDownEventArgs) Handles txtAddress.PreviewKeyDown
   If e.KeyCode = Keys.F4 Then
       Call _Resize()            
       Me.txtAddress.Focus()
    End If
End Sub

COM 对象管理

为了确保在应用程序关闭时可以终止 Excel.exe 进程。我确保释放控件可能使用的任何 COM 对象。我在控件被处置时进行管理。

Private Sub Excel2007RefEdit_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
   
   If xlApp Is Nothing Then Return

   RemoveHandler xlSheet.SelectionChange, AddressOf SelectionChange

   Call NAR(xlSheet)
   Call NAR(xlBook)
   Call NAR(xlApp)
   Call NAR(ExcelConnector)

   DisplayState = Nothing

End Sub 

可用的事件和属性

随着 Excel 2007 组件的发布。我提供了以下事件:

  • Changed - 在 Excel 电子表格上进行选择后触发此事件。
  • DropButtonClicked - 单击下拉按钮后触发此事件。
  • BeforeResize - 在执行大小调整之前触发此事件。此事件可以被取消。
  • AfterResize - 在大小调整执行后触发此事件。
我还包括了以下公共属性:
  • Address - RefEdit 当前选定的值。
  • ImageMinimized - 控件最小化时显示的图像。
  • ImageMaximized - 控件最大化时显示的图像。
  • IncludeSheetName - 指示所选范围是否应包含工作表名称。
  • ShowRowAbsoluteIndicator - 在所选范围中显示行绝对指示符 ($)。
  • ShowColumnAbsoluteIndicator - 在所选范围中显示列绝对指示符 ($)。

我认为公开以下文本框属性很有益:

  • 字体
  • 前景色
  • 从右到左
以及以下按钮属性:

  • FlatAppearance
  • FlatStyle

兴趣点 

这些是我从开发此控件中学到的:

  • 建议处置 .NET 应用程序使用的 Excel COM 对象。
  • 如果您希望当前选中的单元格被控件使用,则必须点击该单元格并再次点击。原因如下:只有当选中的单元格不同时,SelectionChange 事件才会触发。(我仍在寻找解决此问题的方法)。

我相信还有一些需要解决的 bug,并且我会继续改进这个控件。如果您发现任何问题,请告知您是如何修复的,以便我更新控件。

历史

  • v1.0 - 初始发布
  • v1.1 - 添加了示例和源代码
  • v1.2 - 发布了用户控件及其支持的源代码和示例。
  • v1.3 - 更新了控件,使其可以拉伸或收缩。
  • v1.4 - 修复:拉伸或收缩控件时引入的 bug。
           - 添加:使用标准键盘快捷键 (F4) 最小化/最大化控件的功能
                        (F4)
  • v2.0 - 添加:Office 2007 集成,并为控件添加了新功能。
    - 修复:现在可以在 TabControls 等其他容器内使用该控件。
  • v2.1 - 添加:多个公共属性(见上文)。
    - 修复:如果控件位于 groupbox 或 tabcontrol 等容器中,则最大化时控件可能无法始终显示。
                        容器
  • v2.2 - 添加:公开了一些我认为相关的文本框和按钮属性(见上文)
© . All rights reserved.