I'm working on C# and now VB.NET ports of an old VBA program. It has lots of MSForms/OleObjects embedded in it like CommandButton or even images.
My first thought was to declare all the buttons as Microsoft.Vbe.Interop.Forms.CommandButtonbut that leads to a COM exception that the System._COM type can't be cast to ...Forms.CommandButton. If I try a more generic version of this solution, I don't find any items, and if I try to go through all VBComponets I note that they are all the sheets in the workbook, but none of the controls:
foreach (VBComponent xxx in Globals.ThisWorkbook.VBProject.VBComponents) {
Interaction.MsgBox(xxx.Name);
Interaction.MsgBox(xxx.ToString);
}
Thus all of these controls are not in .VBComponets, but I can find them as OLEobjects in thisworkbook.worksheets(n).OLEobjects (this is counterintutive to me, but I probably don't understand the system to begin with).
How do I handle the Click action from such an object?
I'm assuming that I need to be using the Excel.OLEObjectEvents_Event interface, but I can't seem to figure out how. If I try to make custom events with delegates, I don't seem to be able to assign them to OleObjects. If I use ActionClickEventHandler.CreateDelegate I can get a huge variety of errors that makes me think that's a dead end.
The official documentation from MS doesn't seem that helpful, though it did introduce me to the idea of Verb, which I'm looking into. So far that has only produced COM errors along the lines of "Application Failed to start."
Even just trying to use one of the two standard events, .GotFocus, I always pull a 0x80040200 error.
Example:
Excel.OLEObject ButtonCatcher = Globals.ThisWorkbook.Worksheets(1).OLEObjects("CommandButton1");
ButtonCatcher.GotFocus += CommandButton1_Click;
Throws a COMException Exception from HRESULT: 0x80040200 at the second line. The button is enabled, which is I checked after looking up the code number from the office dev site.
Trying a more generic approach within the code for a sheet containing controls:
object CommandButtonStart = this.GetType().InvokeMember("CommandButton1", System.Reflection.BindingFlags.GetProperty, null, this, null);
Throws a Missing Method error.
Any help is greatly appreciated, this seems like this should be obvious and I'm missing it.
**Edit: I have also found that I can cast these controls into Excel.Shape but that doesn't actually get me any closer to running a function or sub from the VSTO. I'm playing with Excel.Shape.OnAction but this requires a VBA sub to be called. Presumably, I could call a VBA sub which calls a sub from the VSTO as long as the VSTO was COM visible. This seems really round-about and I'd only like to do it as a last resort.
Solution Type: VSTO Document-Level
Scenario:
1.) Excel.Worksheet created at run-time. (not a Worksheet Host Item)
2.) Add a button on the Worksheet at run-time that triggers C# code when clicked.
Assembly References:
Microsoft.Vbe.Interop (Microsoft.Vbe.Interop.dll)
Microsoft.Vbe.Interop.Forms (Microsoft.Vbe.Interop.Forms.dll)
Microsoft.VisualBasic (Microsoft.VisualBasic.dll)
Tested / Working Code:
using MSForms = Microsoft.Vbe.Interop.Forms;
using System.Windows.Forms;
...
Microsoft.Vbe.Interop.Forms.CommandButton CmdBtn;
private void CreateOLEButton()
{
Excel.Worksheet ws = Globals.ThisWorkbook.Application.Sheets["MyWorksheet"];
// insert button shape
Excel.Shape cmdButton = ws.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, false, false, Type.Missing, Type.Missing, Type.Missing, 500, 5, 100, 60);
cmdButton.Name = "btnButton";
// bind it and wire it up
CmdBtn = (Microsoft.Vbe.Interop.Forms.CommandButton)Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(ws, null, "btnButton", new object[0], null, null, null);
CmdBtn.Caption = "Click me!";
CmdBtn.Click += new MSForms.CommandButtonEvents_ClickEventHandler(ExecuteCmd_Click);
}
private void ExecuteCmd_Click()
{
MessageBox.Show("Click");
}
Have you tried using NewLateBinding.LateGet?
using MSForms = Microsoft.Vbe.Interop.Forms;
using Microsoft.VisualBasic.CompilerServices;
...
MSForms.CommandButton CommandButton1 = (MSForms.CommandButton)NewLateBinding.LateGet(Globals.ThisWorkbook.Worksheets(1), null, "CommandButton1", new object[0], null, null, null);
CommandButton1.Click += new Microsoft.Vbe.Interop.Forms.CommandButtonEvents_ClickEventHandler(CommandButton1_Click);
It's referenced on MSDN in the VSTO forums and in an old blog post.
Can you programmatically add code to a CodeModule in the Workbook, like this?
Private Sub CommonButton_Click(ByVal buttonName As String)
MsgBox "You clicked button [" & buttonName & "]"
End Sub
Private Sub CreateEventHandler(ByVal buttonName As String)
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Dim codeText As String
Dim LineNum As Long
Set VBComp = ThisWorkbook.VBProject.VBComponents(Me.CodeName)
Set CodeMod = VBComp.CodeModule
LineNum = CodeMod.CountOfLines + 1
codeText = codeText & "Private Sub " & buttonName & "_Click()" & vbCrLf
codeText = codeText & " Dim buttonName As String" & vbCrLf
codeText = codeText & " buttonName = """ & buttonName & "" & vbCrLf
codeText = codeText & " CommonButton_Click buttonName" & vbCrLf
codeText = codeText & "End Sub"
CodeMod.InsertLines LineNum, codeText
End Sub
Use the interop forms toolkit. It is free from Microsoft. It provides com wrappers and event messenger class that communicates the event data to and from .NET to VBA. I have used it to handle control events from VBA in .NET, and events from .NET to VBA. You would use the interop
http://www.codeproject.com/Articles/15690/VB-C-Interop-Form-Toolkit.
From the toolkit ducoumentation:
Interop UserControls provide a basic set of intrinsic events (Click, GotFocus, Validate, etc.) in Visual Basic 6.0. You can define your own custom events in Visual Studio .NET and raise them using RaiseEvent. In order to handle the events in Visual Basic 6.0, you need to add a project reference and add a WithEvents variable declaration in your code, and then handle the events using the WithEvents variable rather than the control itself.
How To Handle Interop UserControl Custom Events
1.
In Visual Basic 6.0, on the Project menu, click References. Note that there is already a reference to ControlNameCtl, where ControlName is the name of your Interop UserControl.
2.
In the Available References list, locate a reference for ControlName and check it, and then click OK.
3.
In the Code Editor, add a declaration for a WithEvents variable:
Dim WithEvents ControlNameEvents As ControlLibrary.ControlName
In the Form_Load event handler, add the following code to initialize the WithEvents variable:
Private Sub Form_Load()
Set ControlNameEvents = Me.ControlNameOnVB6Form
End Sub
Add code to handle your custom event in the WithEvents variable's event handler:
Private Sub ControlNameEvents_MyCustomEvent()
MsgBox("My custom event fired")
End Sub
Thanks Leo Gurdian
In VB.Net look like this:
Assembly References:
Microsoft.Vbe.Interop (Microsoft.Vbe.Interop.dll)
Microsoft.Vbe.Interop.Forms (Microsoft.Vbe.Interop.Forms.dll)
Microsoft.VisualBasic (Microsoft.VisualBasic.dll)
Imports Microsoft.Office.Interop.Excel
Imports Microsoft.VisualBasic
Imports MSForms = Microsoft.Vbe.Interop.Forms
Private Sub CreateOLEButton()
Dim ws As Excel.Worksheet = (CType(Application.ActiveSheet, Excel.Worksheet))
Dim cmdButton As Excel.Shape = ws.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, False, False, Type.Missing, Type.Missing, Type.Missing, 500, 5, 100, 60)
cmdButton.Name = "btnButton"
Dim CmdBtn As Microsoft.Vbe.Interop.Forms.CommandButton = CType(Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(ws, Nothing, "btnButton", New Object(-1) {}, Nothing, Nothing, Nothing), Microsoft.Vbe.Interop.Forms.CommandButton)
CmdBtn.Caption = "Click me!"
AddHandler CmdBtn.Click, AddressOf ExecuteCmd_Click
End Sub
Private Sub ExecuteCmd_Click()
'do something
End Sub
Related
So I'm using this Spotify wrapper here:
Wrapper
and I'm attempting to convert the example provided to VB .NET from C#. I've converted every piece of code except for this one handler section.
Here is the C#:
eh.OnTrackChange += New SpotifyEventHandler.TrackChangeEventHandler(trackchange);
and here is my attempt of converting it to VB:
AddHandler eh.OnTrackChange, New SpotifyEventHandler.TrackChangeEventHandler(AddressOf trackchange)
No errors are thrown, however it simply doesn't work.
Here is TrackChange:
Private Sub trackchange(e As TrackChangeEventArgs)
status.Text = "Now Playing: " + mh.GetCurrentTrack().GetTrackName() + " by " + mh.GetCurrentTrack().GetArtistName()
speak(status.Text)
End Sub
The status text doesn't change and the speak method isn't called. Any help would be appreciated.
Did you set eh.ListenForEvents = true?
Without this set, events won't fire.
Regarding event-syntax, this should be correct:
AddHandler eh.OnTrackChange, AddressOf trackchange
Is it possible to ask Excel to start a C# method?
How would you implement such a call in Excel?
(i.e. instead of programming in VB, I would like to program in C#)
I can imagine using a VB-macro to start a C# application in the background but maybe you know a nicer way?
For example, the C#-code shall be executed upon a click in a particular Excel cell.
Well you could open a program via VBA. This VBA script gets called by clicking on the Excel-Cell:
var Path = "MYPROGRAMPATH"
var Argument = "MYARGUMENT"
x = Shell("""" & Path & """ """ & Argument & """", vbNormalFocus)
To react on a cell change, use the following event:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'YOUR CODE
End Sub
Then program your C# application and let it determine the arguments.
Your program should react according to the filtered arguments.
This can be done with the Environment.GetCommandLineArgs-Method.
public static void Main()
{
Console.WriteLine();
// Invoke this sample with an arbitrary set of command line arguments.
String[] arguments = Environment.GetCommandLineArgs();
Console.WriteLine("GetCommandLineArgs: {0}", String.Join(", ", arguments));
//Handling of arguments here, switch-case, if-else, ...
}
I've found a solution to the VBA macro:
Sub Button1_Click()
Dim pathStr As String
Dim argumentStr As String
pathStr = "C:/Users/.../Desktop/temp/Trial02.exe"
argumentStr = "Any argument string"
Call Shell("""" & pathStr & """ """ & argumentStr & """", vbNormalFocus)
End Sub
I have a form named "form1" in vb.net. This form has many controls. I opened the form1.designer.vb file and put in an if else expression such as:
If getLanguage() = "en" then label1.text = "Good Morning" Else label1.Text = "Bonjour"
This works perfectly fine in runtime.
If I open the Form1.vb [Design] page in design, and make any changes, the code above disappears.
Is there a way I can keep any code I put in the designer page?
I don't want to put them in the load event in the form1.vb file.
Locate this code :
Public Sub New()
Me.InitializeComponent()
End Sub
Then add a line :
Public Sub New()
Me.InitializeComponent()
Me.MakeComponentsChanges()
End Sub
Then create the method in form1.vb or another Partial Class copy of your creation :
Private Sub MakeComponentsChanges()
If getLanguage() = "en" then
label1.text = "Good Morning"
Else
label1.Text = "Bonjour"
End If
End Sub
Don't touch the form1.designer.vb (.cs)
And as stated in other answers, better use .Localizable Property in the IDE and change it from False to True. Then you'll gain access to several default languages. You don't have to bother writing code.
Select ONE language to start with : English for example.
Then edit each one of your controls Text : write "Hello" in a button, "Good Morning" in a Label, "Because I'm Happy" in a MenuItem etc.
Then change the language again, select French.
Then edit again each control and write "Bonjour", "Je vous souhaite un bon matin", "Parceque je suis de bonne humeur".... YES you've lost the previous text but have faith !
Compile your project without launching it, and you'll see the IDE has created two new files : Form1.en.resx and Form1.fr.resx (or so) along with Form1.vb and Form1.Designer.vb. Don't edit them !
If you open the en.resx or fr.resx, you'll see that the edits you've made are in there. Those files are used to store inbuilt Lang-related ressources for your form. That's flatly called Globalization.
Then locate again the constructor of your Form.
Public Sub New()
Me.InitializeComponent()
'Me.MakeComponentsChanges()
' Now you know about some Globalization,
' you may get rid of that Method.
' Add two variables :
Dim OriginalCulture As CultureInfo
Dim CurrentOSCulture As CultureInfo
' Initialize them
OriginalCulture = Thread.CurrentThread.CurrentCulture
CurrentOSCulture = CultureInfo.CurrentCulture
' Do this test :
Try
Thread.CurrentThread.CurrentCulture = CurrentOSCulture ' may fail
Thread.CurrentThread.CurrentUICulture = CurrentOSCulture ' may fail
' Attempt to match the current Thread culture to the Operating System one.
Catch CurrentException As Exception
Thread.CurrentThread.CurrentCulture = OriginalCulture
Thread.CurrentThread.CurrentUICulture = OriginalCulture
' If it fails, revert back to default as defined in your IDE
End Try
End Sub
Don't forget to add on top of your Class declaration those two namespaces :
Imports System.Globalization
Imports System.Threading
And voilà ! I know I said you don't have to bother writing code, but the bits above in the constructor are enough to handle the selection of a language. Plus besoin de taper du code superflu après ça.
For your example maybe better will be using property of the form
.Localizable = true
.Language = Default(French)/English/or others
Visual Studio create two files of one class(which is your form) - Partial Class
So it is doesn't matter in which file you write your code.
Only visual studio use designer.vb file for generating form changes you made by designer
Code from designer.vb contain method InitializeComponent which executed in the constructor.
Create your own method and call it right after InitializeComponent in constructor.
Or create third file for your code if you don't want put in the yourform.vb
File yourForm.MyDesigner.vb
'Using Keyword Partial not necessary anymore,
'because it is used in the `yourForm.Designer.vb`
Public Class yourForm
Public Sub Changelabel()
If getLanguage() = "en" Then
label1.text = "Good Morning"
Else
label1.Text = "Bonjour"
End If
End Sub
End Class
designer.vb files are created automatically by Visual Studio. Every time you edit the design of the form, the file is re-written.
Now, why you don't want to have that code on Load? The load event or the constructor (as already stated) are the right places where to put any initialization you need.
I have added global error handling into my application to catch-all unhandled exceptions. I now just added the functionality to add the bug automatically to my fogbugz account. Now here is my issue.
I added a reference to the dll and also had to add the import declaration for the library. After doing this the code shows no errors. Although as soon as I go to debug the code or build it I get this error:
'BugReport' is not declared. It may be inaccessible due to its protection level.
I am geussing it has to do with some kind of protection? This catch all is in my applicationevents.vb class.
I have tried the same code in another project and it works without error so I know it is not the code. I just don't know what it is? Do I have to change something in my application settings? Here is the code anyways. I replaced the strings with my information for privacy.
Imports FogBugz
Namespace My
' The following events are available for MyApplication:
'
' Startup: Raised when the application starts, before the startup form is created.
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
' UnhandledException: Raised if the application encounters an unhandled exception.
' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.
Partial Friend Class MyApplication
Private Sub MyApplication_UnhandledException(ByVal _
sender As Object, ByVal e As _
Microsoft.VisualBasic.ApplicationServices.UnhandledExceptionEventArgs) _
Handles Me.UnhandledException
'TO DO: SET THESE VALUES BEFORE CALLING THIS METHOD!
Dim url As String = "StackOverFlowDemoString"
'example: http://localhost/fogbugz/scoutSubmit.asp
Dim user As String = "StackOverFlowDemoString"
'existing FogBugz User
Dim project As String = "StackOverFlowDemoString"
'existing FogBugz project
Dim area As String = "StackOverFlowDemoString"
'existing FogBugz area
Dim email As String = "StackOverFlowDemoString"
'email address of the customer who reports the bug
Dim defaultMessage As String = "Bug has been submitted. Every bug submitted helps us make this software that much better. We really do appreciate it."
'the message to return to the user if no Scout Message is found for an existing duplicate bug
Dim forceNewBug As Boolean = False
'If set to true, this forces FogBugz to create a new case for this bug, even if a bug with the same description already exists.
'************************************************************************************
'send the bug we created:
BugReport.Submit(url, user, project, area, email, forceNewBug, _
defaultMessage, e.Exception, True, "{0}.{1}.{2}.{3}", True)
' If the user clicks No, then exit.
e.ExitApplication = _
MessageBox.Show(e.Exception.Message & _
vbCrLf & "Oops! It looks like we have encountered a bug. A bug report has been sent to the developers, so they can have it fixed in a jiffy. Continue?", "An Error has occured.", _
MessageBoxButtons.YesNo, _
MessageBoxIcon.Question) _
= DialogResult.No
End Sub
End Class
End Namespace
The "protection level" refers to the access modifier on your BugReport class.
If you declare a class as Friend (Internal in C#), it is accessible to other classes in the same assembly (.dll).
When you attempt to reference that class from another project, it is not accessible.
You need to change Friend to Public.
After migrating my Visual Studio 2003 projects to VS2005 (or VS2008) my forms would still be inside a single file.
New forms on VS2005 and VS2008 are created using partial classes where all code generated by the editor is kept in the Designer.cs file.
Since the VS2005 form creates is a much better way of dealing with forms, I would like to know if there is a way of converting all my old single-file forms to the VS2005 partial class method.
I've done some by hand but this is very tricky and can lead to some serious errors.
Any suggestions? PS: I'm using Microsoft Visual C# 2008 Express Edition.
This seem to be what you want.
Converting Visual Studio 2003 WinForms to Visual Studio 2005/2008 partial classes :
NET 2.0 introduced partial classes which enables “.designer” files in
Visual Studio 2005 and later. That is, all of the visual
designer-generated code (control declarations, the InitializeComponent
method, etc) can be kept in a file separate from your regular code.
When you open up a .NET 1.x Visual Studio 2003 WinForms project up in
Visual Studio 2005/2008 it will upgrade your project to .NET 2.0 just
fine, but unfortunately it doesn’t migrate your WinForms classes over
to the new “.designer” project structure.
Initially I thought this
would be a job for a DXCore plug-in (the free framework upon which
CodeRush is built) as it provides plug-ins with an object model of the
code which could be used to grab all the right members and move them
over into a designer file. Before I looked into this though I checked
what the options were for simply implementing it as a Visual Studio
Macro. I was fully expecting to have to use a regular expression to
grep the code file to perform the task, but was pleasantly surprised
to find that the Visual Studio extensibility API in available to
macros provides a code model (based on the .NET CodeDom I presume)
which you can traverse to inspect and manipulate the underlying code.
So, here’s what the resulting “ExtractWinFormsDesignerFile” macro
does:
Locates the first class in the selected project item (DTE.SelectedItems.Item(1).ProjectItem) by traversing the
ProjectItem.FileCodeModel.CodeElements
Extracts the InitializeComponent and Dispose methods from the class by traversing CodeClass.Members
Extracts all control fields: that is, all fields whose type derives from System.Windows.Forms.Control or System.ComponentModel.Container
or whose type name starts with System.Windows.Forms
Puts all the extracted code into a new “FormName.Designer.cs” file.
This is currently C# only – it could easily be converted to
generated VB.NET code or adapted use the FileCodeModel properly and
perhaps create the code in an language-agnostic way when generating
the designer file. I took a shortcut in just generating the designer
file as a string and writing it directly to a file.
To “install”:
download the macro text :
' -------------------------------------------------------------------------
' Extract WinForms Designer File Visual Studio 2005/2008 Macro
' -------------------------------------------------------------------------
' Extracts the InitializeComponent() and Dispose() methods and control
' field delarations from a .NET 1.x VS 2003 project into a VS 2005/8
' style .NET 2.0 partial class in a *.Designer.cs file. (Currently C#
' only)
'
' To use:
' * Copy the methods below into a Visual Studio Macro Module (use
' ALT+F11 to show the Macro editor)
' * Select a Windows Form in the Solution Explorer
' * Run the macro by showing the Macro Explorer (ALT+F8) and double
' clicking the 'ExtractWinFormsDesignerFile' macro.
' * You will then be prompted to manually make the Form class partial:
' i.e. change "public class MyForm : Form"
' to
' "public partial class MyForm : Form"
'
' Duncan Smart, InfoBasis, 2007
' -------------------------------------------------------------------------
Sub ExtractWinFormsDesignerFile()
Dim item As ProjectItem = DTE.SelectedItems.Item(1).ProjectItem
Dim fileName As String = item.FileNames(1)
Dim dir As String = System.IO.Path.GetDirectoryName(fileName)
Dim bareName As String = System.IO.Path.GetFileNameWithoutExtension(fileName)
Dim newItemPath As String = dir & "\" & bareName & ".Designer.cs"
Dim codeClass As CodeClass = findClass(item.FileCodeModel.CodeElements)
Dim namespaceName As String = codeClass.Namespace.FullName
On Error Resume Next ' Forgive me :-)
Dim initComponentText As String = extractMember(codeClass.Members.Item("InitializeComponent"))
Dim disposeText As String = extractMember(codeClass.Members.Item("Dispose"))
Dim fieldDecls As String = extractWinFormsFields(codeClass)
On Error GoTo 0
System.IO.File.WriteAllText(newItemPath, "" _
& "using System;" & vbCrLf _
& "using System.Windows.Forms;" & vbCrLf _
& "using System.Drawing;" & vbCrLf _
& "using System.ComponentModel;" & vbCrLf _
& "using System.Collections;" & vbCrLf _
& "" & vbCrLf _
& "namespace " & namespaceName & vbCrLf _
& "{" & vbCrLf _
& " public partial class " & codeClass.Name & vbCrLf _
& " {" & vbCrLf _
& " #region Windows Form Designer generated code" & vbCrLf _
& " " & fieldDecls & vbCrLf _
& " " & initComponentText & vbCrLf _
& " #endregion" & vbCrLf & vbCrLf _
& " " & disposeText & vbCrLf _
& " }" & vbCrLf _
& "}" & vbCrLf _
)
Dim newProjItem As ProjectItem = item.ProjectItems.AddFromFile(newItemPath)
On Error Resume Next
newProjItem.Open()
DTE.ExecuteCommand("Edit.FormatDocument")
On Error GoTo 0
MsgBox("TODO: change your class from:" + vbCrLf + _
" ""public class " + codeClass.FullName + " : Form""" + vbCrLf + _
"to:" + _
" ""public partial class " + codeClass.FullName + " : Form""")
End Sub
Function findClass(ByVal items As System.Collections.IEnumerable) As CodeClass
For Each codeEl As CodeElement In items
If codeEl.Kind = vsCMElement.vsCMElementClass Then
Return codeEl
ElseIf codeEl.Children.Count > 0 Then
Dim cls As CodeClass = findClass(codeEl.Children)
If cls IsNot Nothing Then
Return findClass(codeEl.Children)
End If
End If
Next
Return Nothing
End Function
Function extractWinFormsFields(ByVal codeClass As CodeClass) As String
Dim fieldsCode As New System.Text.StringBuilder
For Each member As CodeElement In codeClass.Members
If member.Kind = vsCMElement.vsCMElementVariable Then
Dim field As CodeVariable = member
If field.Type.TypeKind <> vsCMTypeRef.vsCMTypeRefArray Then
Dim fieldType As CodeType = field.Type.CodeType
Dim isControl As Boolean = fieldType.Namespace.FullName.StartsWith("System.Windows.Forms") _
OrElse fieldType.IsDerivedFrom("System.Windows.Forms.Control") _
OrElse fieldType.IsDerivedFrom("System.ComponentModel.Container")
If isControl Then
fieldsCode.AppendLine(extractMember(field))
End If
End If
End If
Next
Return fieldsCode.ToString()
End Function
Function extractMember(ByVal memberElement As CodeElement) As String
Dim memberStart As EditPoint = memberElement.GetStartPoint().CreateEditPoint()
Dim memberText As String = String.Empty
memberText += memberStart.GetText(memberElement.GetEndPoint())
memberStart.Delete(memberElement.GetEndPoint())
Return memberText
End Function
and copy the methods into a Visual Studio
Macro Module (use ALT+F11 to show the Macro editor).
To use:
Select a Windows Form in the Solution Explorer
Run the macro by showing the Macro Explorer (ALT+F8) and double-clicking the ‘ExtractWinFormsDesignerFile’ macro. (Obviously
you can hook the macro up to a toolbar button if you like.)
You will then be prompted to manually make the Form class partial (another bit I was too lazy to work out how to get the macro to do):
i.e. change public class MyForm : Form to public partial class
MyForm : Form
As you're probably aware, all the Express editions do not support third party extensions. Unfortunately I know of no stand alone tools that can do what you are asking.
I've experimented with splitting a Winform class into partials classes. As you discovered, it is not a trivial undertaking. This question has been asked before. Unlike Martin's attempt, I went the other direction. Instead of creating a designer file, I renamed the existing file to MyForm.Designer.cs and created a new MyForm.cs file. I then proceeded in a similar manner, moving the "code behind" instead of the designer code into my new class.
The one sticking point with either of these techniques is that future changes to the form still don't generate in the correct class file. This is because the project file still doesn't recognize the two files to be linked together. Your only option is to manually edit the project file in a text editor. Look for the following:
<Compile Include="MyForm.Designer.cs">
<SubType>Form</SubType>
</Compile>
Replace the <SubType>...</SubType> with <DependentUpon>MyForm.cs</DependentUpon> so the end result looks like:
<Compile Include="MyForm.Designer.cs">
<DependentUpon>MyForm.cs</DependentUpon>
</Compile>
Another solution I experimented with was simply creating an new form and dragging the controls from the old form to it. This actually worked to an extent. All the controls migrated along with all their properties. What didn't migrate was event handlers. These you would have to cut and paste from the old form, then go through each control and reselect the appropriate handler from the form designer. Depending on the complexity of the form this might be a reasonable alternative.
From my own personal experiences supporting multiple UIs the best approach is to keep form design simple and separate the business logic from the UI completely. The MVP Passive view works pretty well for this. By delegating as much of the responsibility to a presenter class it becomes trivial to implement the form in a different UI framework. WinForms, WebForms, WPF, etc, it makes little difference to the presenter class. All it sees in an interface exposing a list of properties it manipulates. Of course all the shoulda coulda wouldas in the world won't help when the problem you are facing is here and now.