I am currently upgrading our software from VB6 to VB.NET and doing so one DLL at a time. The EXE will be last since it has a lot of third party UI controls.
One issue that I found is that the VB6 version of the DLL housed an IProgressCallBack class that was acting like an interface class (which does not officially exist in VB6) and allowed the EXE to get progress reports from the DLL and display it on the screen for the user.
When this DLL is migrated the IProgressCallBack class can be set as an Interface class, but then it cannot be 'Set' to the instance of the form using it. But if I create it as just a normal class, it still will not let me 'Set' it to the instance of the form using it since they are two different object types.
I need to know how do I either make the IProgressCallBack class work between VB6 and VB.NET or what is an alternate solution to keep the VB6 UI informed of the progress of the .NET DLL?
Here is an example of the VB6 version of both and how they work:
VB6 DLL: IProgressCallBack
Option Explicit
Public Function NotifyProgress(ByVal Progress As Single, ByRef isCancel As Boolean) As Boolean
End Function
VB6 DLL: MainFunctions Class
Public ProgressNotify As IProgressCallBack
Public Sub DoWork()
Dim l As Long
For l = 1 to 100
Sleep 10 'sleep for 1/100 of a second
ProgressNotify.NotifyProgress 1, False
Next
End Sub
VB6 EXE
Implements IProgressCallBack
Dim m_oMainFunc As MainFunctions
Private Sub cmdStart_Click()
Set m_oMainFunc = New MainFunctions
Set m_oMainFunc.ProgressNotify = Me
m_oMainFunc.DoWork
Set m_oMainFunc = Nothing
End Sub
Public Function IProgressCallBack_NotifyProgress(ByVal Progress As Single, isCancel As Boolean) As Boolean
lblStatus.Caption = CStr(Round(Progress)) & "%"
pbStatus.Value = Round(Progress)
Me.Refresh
End Function
Thanks,
Chris
I have not exactly found the answer to the question I posted above, but I have found a nice workaround.
Essentially I pass in the form as an object to the class pretending to be an interface and directly call the interface method that resides on the form to update the screen.
Here is the code sample of how I do this:
VB.NET DLL: IProgressCallBack Class
Dim m_oForm As Object
Public Sub New(ByRef oForm As Object)
m_oForm = oForm
End Sub
Public Function NotifyProgress(ByVal Progress As Single, ByRef isCancel As Boolean) As Boolean
m_oForm.IProgressCallBack_NotifyProgress(Progress, isCancel)
End Function
VB.NET DLL: MainFunctions Class
Public ProgressNotify As IProgressCallBack
Public Sub New()
'Need Public Sub New() with no parameters for all Com Classes
MyBase.New()
End Sub
Public Sub Initialize(ByRef oForm As Object)
ProgressNotify = New IProgressCallBack(oForm)
End Sub
Public Sub DoWork()
For l As Long = 1 to 100
Threading.Thread.Sleep(10) 'sleep for 1/100 of a second
ProgressNotify.NotifyProgress(l,False)
Next
End Sub
VB6 EXE
Dim m_oMainFunc As TestDLL_Net.MainFunctions
Private cmdStart_Click()
Set m_oMainFunc = New TestDLL_Net.MainFunctions
m_oMainFunc.Initialize Me
m_oMainFunc.DoWork
Set m_oMainFunc = Nothing
End Sub
Public Function IProgressCallBack_NotifyProgress(ByVal Progress As Single, isCancel As Boolean) As Boolean
DoEvents
lblStatus.Caption = CStr(Round(Progress)) & "%"
Me.Refresh
pbStatus.Value = Round(Progress)
End Function
This provides the same result as the VB6 DLL to VB6 EXE example posted above, and the changes in the EXE are minimal to make it work.
Thanks,
Chris
Related
I have the following C# method:
private static bool IsLink(string shortcutFilename)
{
var pathOnly = Path.GetDirectoryName(shortcutFilename);
var filenameOnly = Path.GetFileName(shortcutFilename);
var shell = new Shell32.Shell();
var folder = shell.NameSpace(pathOnly);
var folderItem = folder.ParseName(filenameOnly);
return folderItem != null && folderItem.IsLink;
}
I have tried converting this to F# as:
let private isLink filename =
let pathOnly = Path.GetDirectoryName(filename)
let filenameOnly = Path.GetFileName(filename)
let shell = new Shell32.Shell()
let folder = shell.NameSpace(pathOnly)
let folderItem = folder.ParseName(filenameOnly)
folderItem <> null && folderItem.IsLink
It however reports an error for the let shell = new Shell32.Shell() line, saying that new cannot be used on interface types.
Have I just made a silly syntactic mistake, or is there extra work needed to access COM from F#?
I don't know enough about the F# compiler but your comments makes it obvious enough. The C# and VB.NET compilers have a fair amount of explicit support for COM built-in. Note that your statement uses the new operator on an interface type, Shell32.Shell in the interop library looks like this:
[ComImport]
[Guid("286E6F1B-7113-4355-9562-96B7E9D64C54")]
[CoClass(typeof(ShellClass))]
public interface Shell : IShellDispatch6 {}
IShellDispatch6 is the real interface type, you can also see the IShellDispatch through IShellDispatch5 interfaces. That's versioning across the past 20 years at work, COM interface definitions are immutable since changing them almost always causes an undiagnosable hard crash at runtime.
The [CoClass] attribute is the important one for this story, that's what the C# compiler goes looking for you use new on a [ComImport] interface type. Tells it to create the object by creating an instance of Shell32.ShellClass instance and obtain the Shell interface. What the F# compiler doesn't do.
ShellClass is a fake class, it is auto-generated by the type library importer. COM never exposes concrete classes, it uses a hyper-pure interface-based programming paradigm. Objects are always created by an object factory, CoCreateInstance() is the workhorse for that. Itself a convenience function, the real work is done by the universal IClassFactory interface, hyper-pure style. Every COM coclass implements its CreateInstance() method.
The type library importer makes ShellClass look like this:
[ComImport]
[TypeLibType(TypeLibTypeFlags.FCanCreate)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("13709620-C279-11CE-A49E-444553540000")]
public class ShellClass : IShellDispatch6, Shell {
// Methods
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(0x60040000)]
public virtual extern void AddToRecent([In, MarshalAs(UnmanagedType.Struct)] object varFile, [In, Optional, MarshalAs(UnmanagedType.BStr)] string bstrCategory);
// Etc, many more methods...
}
Lots of fire and movement, none of it should ever be used. The only thing that really matters is the [Guid] attribute, that provides the CLSID that CoCreateInstance() needs. It also needs the IID, the [Guid] of the interface, provided by the interface declaration.
So the workaround in F# is to create the Shell32.ShellClass object, just like the C# compiler does implicitly. While technically you can keep the reference in a ShellClass variable, you should strongly favor the interface type instead. The COM way, the pure way, it avoids this kind of problem. Ultimately it is the CLR that gets the job done, it recognizes the [ClassInterface] attribute on the ShellClass class declaration in its new operator implementation. The more explicit way in .NET is to use Type.GetTypeFromCLSID() and Activator.CreateInstance(), handy when you only have the Guid of the coclass.
So here is the thing ..
I wrote a c# application to generate monthly Attendance Reports for each employee with his own details
I want to be able to do this once for all employees and view the report grouped by name
so when I select the customer name from the crystal report sub tree I get his monthly Attendance Report
I don't really know how to use the sub tree in crystal report ... is it possible to something like that ?
the goal from all this is to be able to print all reports at once in one click
This is not exactly what you asked for but I am going to post it because it may work for you. I did for me in a similar situation. Also sorry about the VB syntax
This will allow you to create your reports as PDFs using the Crystal Reports engine. Basically it will allow you to create multiple PDF using a loop, which can then be printed automatically.The Export PDF Sub will write the file to disk and then open it with the default pdf reader. The print PDF function will automaticaly print the PDF files that were saved to disk. This is not a perfect solution but I hope that it at least gets you closer to what you are trying to accomplish.
Public Class PDFCR
Private Const SW_SHOWNORMAL As Integer = 2
<DllImport("shell32")> _
Public Shared Function ShellExecute(ByVal hWnd As IntPtr, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Integer) As IntPtr
End Function
Public Shared Sub ExportPDF(ByVal crDOC As ReportDocument, ByVal FilePath As String)
Dim CrExportOptions As ExportOptions
Dim CrDiskFileDestinationOptions As New DiskFileDestinationOptions()
Dim CrFormatTypeOptions As New PdfRtfWordFormatOptions()
CrDiskFileDestinationOptions.DiskFileName = FilePath
CrExportOptions = crDOC.ExportOptions
CrExportOptions.ExportDestinationType = ExportDestinationType.DiskFile
CrExportOptions.ExportFormatType = ExportFormatType.PortableDocFormat
CrExportOptions.DestinationOptions = CrDiskFileDestinationOptions
CrExportOptions.FormatOptions = CrFormatTypeOptions
crDOC.Export()
Process.Start(FilePath)
End Sub
Public Shared Function PrintPDF(ByVal FilePath As String) As Boolean
If IO.File.Exists(FilePath) Then
If ShellExecute(CType(1, IntPtr), "Print", FilePath, "", _
Directory.GetDirectoryRoot(FilePath), SW_SHOWNORMAL).ToInt32 <= 32 Then
Return False
Else
Return True
End If
Else
Return False
End If
End Function
End Class
I was having trouble getting the Imports to show in this code block so here they are in plain text.
Imports System.IO
Imports System.Management
Imports CrystalDecisions.Shared
Imports System.Runtime.InteropServices
Imports CrystalDecisions.CrystalReports.Engine
If you add a GROUP to your report on your Employee Name field, this will (by default) create the group tree you are looking for.
From there, code-wise, it can be turned off, but you should see the group tree by default if your report has any groups in it.
The problem seems to be with the report not being grouped on Employee Name.
I'll start by telling my project setup:
ASP.NET MVC 1.0
StructureMap 2.6.1
VB
I've created a bootstrapper class shown here:
Imports StructureMap
Imports DCS.Data
Imports DCS.Services
Public Class BootStrapper
Public Shared Sub ConfigureStructureMap()
ObjectFactory.Initialize(AddressOf StructureMapRegistry)
End Sub
Private Shared Sub StructureMapRegistry(ByVal x As IInitializationExpression)
x.AddRegistry(New MainRegistry())
x.AddRegistry(New DataRegistry())
x.AddRegistry(New ServiceRegistry())
x.Scan(AddressOf StructureMapScanner)
End Sub
Private Shared Sub StructureMapScanner(ByVal scanner As StructureMap.Graph.IAssemblyScanner)
scanner.Assembly("DCS")
scanner.Assembly("DCS.Data")
scanner.Assembly("DCS.Services")
scanner.WithDefaultConventions()
End Sub
End Class
I've created a controller factory shown here:
Imports System.Web.Mvc
Imports StructureMap
Public Class StructureMapControllerFactory
Inherits DefaultControllerFactory
Protected Overrides Function GetControllerInstance(ByVal controllerType As System.Type) As System.Web.Mvc.IController
Return ObjectFactory.GetInstance(controllerType)
End Function
End Class
I've modified the Global.asax.vb as shown here:
...
Sub Application_Start()
RegisterRoutes(RouteTable.Routes)
'StructureMap
BootStrapper.ConfigureStructureMap()
ControllerBuilder.Current.SetControllerFactory(New StructureMapControllerFactory())
End Sub
...
I've added a Structure Map registry file to each of my three projects: DCS, DCS.Data, and DCS.Services. Here is the DCS.Data registry:
Imports StructureMap.Configuration.DSL
Public Class DataRegistry
Inherits Registry
Public Sub New()
'Data Connections.
[For](Of DCSDataContext)() _
.HybridHttpOrThreadLocalScoped _
.Use(New DCSDataContext())
'Repositories.
[For](Of IShiftRepository)() _
.Use(Of ShiftRepository)()
[For](Of IMachineRepository)() _
.Use(Of MachineRepository)()
[For](Of IShiftSummaryRepository)() _
.Use(Of ShiftSummaryRepository)()
[For](Of IOperatorRepository)() _
.Use(Of OperatorRepository)()
[For](Of IShiftSummaryJobRepository)() _
.Use(Of ShiftSummaryJobRepository)()
End Sub
End Class
Everything works great as far as loading the dependecies, but I'm having problems with the DCSDataContext class that was genereated by Linq2SQL Classes.
I have a form that posts to a details page (/Summary/Details), which loads in some data from SQL. I then have a button that opens a dialog box in JQuery, which populates the dialog from a request to (/Operator/Modify). On the dialog box, the form has a combo box and an OK button that lets the user change the operator's name. Upon clicking OK, the form is posted to (/Operator/Modify) and sent through the service and repository layers of my program and updates the record in the database. Then, the RedirectToAction is called to send the user back to the details page (/Summary/Details) where there is a call to pull the data from SQL again, updating the details view.
Everything works great, except the details view does not show the new operator that was selected. I can step through the code and see the DCSDataContext class being accessed to update the operator (which does actually change the database record), but when the DCSDataContext is accessed to reload the details objects, it pulls in the old value. I'm guessing that StructureMap is causing not only the DCSDataContext class but also the data to be cached?
I have also tried adding the following to the Global.asax, but it just ends up crashing the program telling me the DCSDataContext has been disposed...
Private Sub MvcApplication_EndRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.EndRequest
StructureMap.ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects()
End Sub
Can someone please help?
Got this response back from Jeremy Miller on the StructureMap google group:
Easy money, you're creating an instance of your DataContext object -yourself- in the registration, which de facto makes that a singleton throughout the StructureMap ecosystem.
This code:
'Data Connections.
[For](Of DCSDataContext)() _
.HybridHttpOrThreadLocalScoped _
.Use(New DCSDataContext())
Needs to define the DataContext using -deferred- execution rather than using the pre-built "New DCSDataContext()"
If you were in C# (because I don't know the VB syntax), you would do:
For<DCSDataContext>().HybridHttpOrThreadLocalScoped().Use(() => new DCSDataContext());
I ran this through a C# to VB converter and it gave me this:
[For](Of DCSDataContext)() _
.HybridHttpOrThreadLocalScoped _
.Use(Function() New DCSDataContext())
Which works great!
I work in a VB.Net environment and have recently been tasked with creating an MVC environment to use as a base to work from. I decided to convert the latest SharpArchitecture release (Q3 2009) into VB, which on the whole has gone fine after a bit of hair pulling. I came across a problem with Castle Windsor where my custom repository interface (lives in the core/domain project) that was reference in the constructor of my test controller was not getting injected with the concrete implementation (from the data project). I hit a brick wall with this so basically decided to switch out Castle Windsor for StructureMap.
I think I have implemented this ok as everything compiles and runs and my controller ran ok when referencing a custom repository interface. It appears now that I have/or cannot now setup my generic interfaces up properly (I hope this makes sense so far as I am new to all this). When I use IRepository(Of T) (wanting it to be injected with a concrete implementation of Repository(Of Type)) in the controller constructor I am getting the following runtime error:
"StructureMap Exception Code: 202 No Default Instance defined for PluginFamily SharpArch.Core.PersistenceSupport.IRepository`1[[DebtRemedy.Core.Page, DebtRemedy.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], SharpArch.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b5f559ae0ac4e006"
Here are my code excerpts that I am using (my project is called DebtRemedy).
My structuremap registry class
Public Class DefaultRegistry
Inherits Registry
Public Sub New()
''//Generic Repositories
AddGenericRepositories()
''//Custom Repositories
AddCustomRepositories()
''//Application Services
AddApplicationServices()
''//Validator
[For](GetType(IValidator)).Use(GetType(Validator))
End Sub
Private Sub AddGenericRepositories()
''//ForRequestedType(GetType(IRepository(Of ))).TheDefaultIsConcreteType(GetType(Repository(Of )))
[For](GetType(IEntityDuplicateChecker)).Use(GetType(EntityDuplicateChecker))
[For](GetType(IRepository(Of ))).Use(GetType(Repository(Of )))
[For](GetType(INHibernateRepository(Of ))).Use(GetType(NHibernateRepository(Of )))
[For](GetType(IRepositoryWithTypedId(Of ,))).Use(GetType(RepositoryWithTypedId(Of ,)))
[For](GetType(INHibernateRepositoryWithTypedId(Of ,))).Use(GetType(NHibernateRepositoryWithTypedId(Of ,)))
End Sub
Private Sub AddCustomRepositories()
Scan(AddressOf SetupCustomRepositories)
End Sub
Private Shared Sub SetupCustomRepositories(ByVal y As IAssemblyScanner)
y.Assembly("DebtRemedy.Core")
y.Assembly("DebtRemedy.Data")
y.WithDefaultConventions()
End Sub
Private Sub AddApplicationServices()
Scan(AddressOf SetupApplicationServices)
End Sub
Private Shared Sub SetupApplicationServices(ByVal y As IAssemblyScanner)
y.Assembly("DebtRemedy.ApplicationServices")
y.With(New FirstInterfaceConvention)
End Sub
End Class
Public Class FirstInterfaceConvention
Implements ITypeScanner
Public Sub Process(ByVal type As Type, ByVal graph As PluginGraph) Implements ITypeScanner.Process
If Not IsConcrete(type) Then
Exit Sub
End If
''//only works on concrete types
Dim firstinterface = type.GetInterfaces().FirstOrDefault()
''//grabs first interface
If firstinterface IsNot Nothing Then
graph.AddType(firstinterface, type)
Else
''//registers type
''//adds concrete types with no interfaces
graph.AddType(type)
End If
End Sub
End Class
I have tried both ForRequestedType (which I think is now deprecated) and For.
IRepository(Of T) lives in SharpArch.Core.PersistenceSupport.
Repository(Of T) lives in SharpArch.Data.NHibernate.
My servicelocator class
Public Class StructureMapServiceLocator
Inherits ServiceLocatorImplBase
Private container As IContainer
Public Sub New(ByVal container As IContainer)
Me.container = container
End Sub
Protected Overloads Overrides Function DoGetInstance(ByVal serviceType As Type, ByVal key As String) As Object
Return If(String.IsNullOrEmpty(key), container.GetInstance(serviceType), container.GetInstance(serviceType, key))
End Function
Protected Overloads Overrides Function DoGetAllInstances(ByVal serviceType As Type) As IEnumerable(Of Object)
Dim objList As New List(Of Object)
For Each obj As Object In container.GetAllInstances(serviceType)
objList.Add(obj)
Next
Return objList
End Function
End Class
My controllerfactory class
Public Class ServiceLocatorControllerFactory
Inherits DefaultControllerFactory
Protected Overloads Overrides Function GetControllerInstance(ByVal requestContext As RequestContext, ByVal controllerType As Type) As IController
If controllerType Is Nothing Then
Return Nothing
End If
Try
Return TryCast(ObjectFactory.GetInstance(controllerType), Controller)
Catch generatedExceptionName As StructureMapException
System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave())
Throw
End Try
End Function
End Class
The initialise stuff in my global.asax
Dim container As IContainer = New Container(New DefaultRegistry)
ControllerBuilder.Current.SetControllerFactory(New ServiceLocatorControllerFactory())
ServiceLocator.SetLocatorProvider(Function() New StructureMapServiceLocator(container))
My test controller
Public Class DataCaptureController
Inherits BaseController
Private ReadOnly clientRepository As IClientRepository()
Private ReadOnly pageRepository As IRepository(Of Page)
Public Sub New(ByVal clientRepository As IClientRepository(), ByVal pageRepository As IRepository(Of Page))
Check.Require(clientRepository IsNot Nothing, "clientRepository may not be null")
Check.Require(pageRepository IsNot Nothing, "pageRepository may not be null")
Me.clientRepository = clientRepository
Me.pageRepository = pageRepository
End Sub
Function Index() As ActionResult
Return View()
End Function
The above works fine when I take out everything to do with the pageRepository which is IRepository(Of T).
Any help with this would be greatly appreciated.
I had a similar issue yesterday with instantiating IRepository(Of MyEntity).
I had to state y.ConnectImplementationsToTypesClosing(GetType(IRepository(Of ))) in my Scan delegate to make StructureMap map generic types to their implementation.
Like this:
Private Shared Sub SetupCustomRepositories(ByVal y As IAssemblyScanner)
y.Assembly("DebtRemedy.Core")
y.Assembly("DebtRemedy.Data")
y.WithDefaultConventions()
y.ConnectImplementationsToTypesClosing(GetType(Of ));
End Sub
Make sure you are only creating one container.
I also converted a C# project from Castle Windsor to StructureMap. The original CW-based project instantiated a Container in Application_Start() (MVC2 project) and passed it around for configuration. I kept the same approach without thinking, kinda when you translate from Spanish to English litterally, and it's just as bad. :)
What happened is that I ended up creating a second SM container. StructureMap's container is static, and so there's always one "in the background". If you new up a container, you actually create a second, independent container. if you aren't careful, you end up sometimes using one, sometimes the other, and get a plague of " No Default Instance" errors at various points when you know it's defined..
The way I came across it is that I ended up littering my code with WhatDoIHave() calls, which was fortunate because I noted that sometimes I saw a configured container (the second) and sometimes I saw the static one (the first), which had not been configured. Different GUID names was the giveaway.
Check if the same is happening in your VB code.
Not that familiar with this, but it looks like it may not be registered with the container or because the resolver is greedy it might choose a constructor that does not have registered items. The following URL looks very similar to the same problem take a look...
http://learningbyfailing.com/2010/02/structuremap-exception-no-default-instance-defined-for-pluginfamily-iformsauthentication/
As part of a unit test I am trying to mock the return value of FormsIdentity.Ticket.UserData
The following will NOT work but it should give an idea of what I am trying to do:
var principal = Mock<IPrincipal>();
var formsIdentity = Mock<FormsIdentity>();
formsIdentity.Setup(a => a.Ticket.UserData).Returns("aaa | bbb | ccc");
principal.Setup(b => b.Identity).Returns(formsIdentity.Object);
The code I am trying to test looks something like this:
FormsIdentity fIdentity = HttpContext.Current.User.Identity as FormsIdentity;
string userData = fIdentity.Ticket.UserData;
All I want to do in my unit test is fake the return value from FormsIdentity.Ticket.UserData. But when I run the code in the first section I get an error when trying to mock the FormsIdentity. The error says the type to mock must be an interface, abstract class or non-sealed class.
I tried to use IIdentity instead of FormsIdentity (FormsIdentity is an implementation of IIdentity) but IIdentity doesn't have .Ticket.UserData.
So how can I write this test so that I get a value from FormsIdentity.Ticket.UserData?
I'm not a Unit Test expert, by any means, just getting my feet wet in the area.
Isn't it overkill to mock out the Identity in a unit test, because the Identity code is code that you can assume works already in isolation? (ie. it's Microsoft's code?) For example, when unit testing your own code, you wouldn't need to mock out one of the Framework objects. I mean, would you ever need to mock a List or a Dictionary?
That being said, if you REALLY want to test your code in isolation or for some reason have super fine control over the data returned in Userdata, can't you just write an Interface for the interaction between the Identity and your code?
Public Interface IIdentityUserData
Readonly Property UserData As String
End Interface
Public Class RealIdentityWrapper
Implements IIdentityUserData
Private _identity as FormsIdentity
Public Sub New(identity as FormsIdentity)
'the real version takes in the actual forms identity object
_identity = identity
End Sub
Readonly Property UserData As String Implements IIDentityUserData.UserData
If not _identity is nothing then
Return _identity.Ticket.UserData
End If
End Property
End Class
'FAKE CLASS...use this instead of Mock
Public Class FakeIdentityWrapper
Implements IIdentityUserData
Readonly Property UserData As String Implements IIDentityUserData.UserData
If not _identity is nothing then
Return "whatever string you want"
End If
End Property
End Class
'here's the code that you're trying to test...modified slightly
Dim fIdentity As FormsIdentity= HttpContext.Current.User.Identity
Dim identityUserData As IIdentityUserData
identityUserData =
'TODO: Either the Real or Fake implementation. If testing, inject the Fake implementation. If in production, inject the Real implementation
Dim userData as String
userData = identityUserData.UserData
Hope this helps