How to properly render an embedded Font? - vb.net

I download a True Type Font and I embedded it just as this page explains.
I had to set the UseCompatibleTextRendering property to be able to load it but it looks very weird, I don't know why it looks good in the browser but not in the application.
Just to be clear I added the font to my resources, set it as embedded resource, I used this module:
Imports System.IO
Imports System.Reflection
Imports System.Drawing.Text
Imports System.Runtime.InteropServices
Module ExternalFontType
Public Function GetFont(aAssembly As Assembly,
strFontName As String, intFontSize As Integer,
fsFontStyle As FontStyle) As Font
Using pcolFonts As New PrivateFontCollection
Dim bFont() As Byte = ExternalFontType.bRawFontData(aAssembly, strFontName)
Dim ptrMemFont As IntPtr =
Marshal.AllocCoTaskMem(bFont.Length)
Marshal.Copy(bFont, 0, ptrMemFont, bFont.Length)
pcolFonts.AddMemoryFont(ptrMemFont, bFont.Length)
Marshal.FreeCoTaskMem(ptrMemFont)
Return New Font(pcolFonts.Families(0),
intFontSize, fsFontStyle)
End Using
End Function
Private Function bRawFontData(aAssembly As Assembly, strFontName As String) As Byte()
Using stFont As Stream =
aAssembly.GetManifestResourceStream(strFontName)
If (stFont Is Nothing) Then Throw _
New Exception(String.Format("Cannot load _
font '{0}'", strFontName))
Dim bFontBuffer() As Byte = New _
Byte(CInt(stFont.Length - 1)) {}
stFont.Read(bFontBuffer, 0, CInt(stFont.Length))
Return bFontBuffer
End Using
End Function
End Module
and included it in this code
lbl.UseCompatibleTextRendering = True
lbl.Font = ExternalFontType.GetFont(Me.GetType.Assembly, "ProyectName.FontName.ttf", 15, FontStyle.Bold)

More than one problem with that code:
The PrivateFontCollection cannot be declared with a Using statement: this collection must be preserved as long as the Fonts it points to are needed. It's usually declared as a Field in the class (Form) that uses it or in a shared class (or Module, here), then disposed of when not needed anymore.
Marshal.FreeCoTaskMem() cannot be used here; it's a temptation to call it after Marshal.AllocCoTaskMem(), but not in this occasion. This can (will) compromise the Font data allocation. What you need to do is dispose of the PrivateFontcollection object. The Framework will take care of the COM affair (it will do it for you even if you forget to dispose of the PrivateFontcollection object. You should try not to forget, though).
The assembly reference is not required: the Font is added to the Project's Resources as a byte array, which is all that's needed. It can then be retrieved either by name, e.g., My.Resources.SomeFontName, or using the ResourceManager.GetObject() method, casting the returned object to Byte():
Dim fontData As Byte() = My.Resources.SomeFontName
Dim fontData As Byte() = DirectCast(My.Resources.ResourceManager.GetObject("SomeFontName"), Byte())
▶ You have already mentioned this but let's say it again: not all controls can use these Fonts. Only controls that can use Fonts drawn by GDI+ can actually use Fonts from the PrivateFontCollection, the Label and Button controls are among of these, in fact both expose a UseCompatibleTextRendering property. A RichTextBox, for example, cannot.
If the Font is created correctly, you can use Graphics.DrawString() to draw strings content using that Font, even when you cannot set it as the Font of a Control.
Private myFontCollection As PrivateFontCollection = New PrivateFontCollection()
In the Form's Constructor, add Font from the Project's Resources.
Here I'm using a helper class, FontManager, which exposes a public shared method AddFontsFromResource(): pass to this method the PrivateFontCollection and a list of resources names corresponding to the Font names.
This method fills the collection with Fonts that can be installed successfully and returns the number of Fonts installed.
Of course you use whatever other method you prefer to reference your Fonts.
Note. In the example, three Font resources are added to the collection:
{"FontFamily1Regular", "FontFamily1Italics", "OtherFontFamily"}
but two belong to the same FontFamily, so the PrivateFontCollection will contain just two elements, not three.
Public Sub New()
Dim installedFontsCount = FontManager.AddFontsFromResources(myFontCollection,
{"FontFamily1Regular", "FontFamily1Italics", "OtherFontFamily"})
' The Font can set here or anywhere else
someLabel.UseCompatibleTextRendering = True
someLabel.Font = New Font(myFontCollection.Families(0), 10.5F, FontStyle.Regular)
someButton.UseCompatibleTextRendering = True
someButton.Font = New Font(myFontCollection.Families(0), 10.5F, FontStyle.Italic)
End Sub
It's important to dispose of the PrivateFontCollection when it's not needed anymore: when the Form that initialized it closes or before the Application closes:
You could also use a shared object to reference a PrivateFontCollection that can be used anywhere in the Project. In this case the collection needs to be disposed of when the Application closes.
Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
myFontCollection.Dispose()
End Sub
Helper class:
Imports System.Drawing.Text
Imports System.Runtime.InteropServices
Public Class FontManager
Public Shared Function AddFontsFromResources(fontCollection As PrivateFontCollection, fontNames As String()) As Integer
If fontNames.Length = 0 Then Return Nothing
Dim installedFontsCount = 0
For Each fontName As String In fontNames
Try
Dim fontData As Byte() = CType(My.Resources.ResourceManager.GetObject(fontName), Byte())
If fontData Is Nothing Then Throw New InvalidOperationException()
Dim data As IntPtr = Marshal.AllocCoTaskMem(fontData.Length)
Marshal.Copy(fontData, 0, data, fontData.Length)
fontCollection.AddMemoryFont(data, fontData.Length)
installedFontsCount += 1
Catch ex As Exception
' Placeholder: Notify User/Log/Whatever
Debug.Print($"Font installation failed for {fontName}")
End Try
Next
Return installedFontsCount
End Function
End Class
C# version:
using System.Drawing.Text;
using System.Runtime.InteropServices;
public static int AddFontsFromResources(PrivateFontCollection fontCollection, string[] fontNames)
{
int installedFontsCount = 0;
if (fontNames.Length == 0) return 0;
foreach (string fontName in fontNames) {
try {
byte[] fontData = (byte[])Properties.Resources.ResourceManager.GetObject(fontName);
var data = Marshal.AllocCoTaskMem(fontData.Length);
Marshal.Copy(fontData, 0, data, fontData.Length);
fontCollection.AddMemoryFont(data, fontData.Length);
installedFontsCount += 1;
}
catch (Exception) {
// Placeholder: Notify User/Log/Whatever
Console.WriteLine($"Font installation failed for {fontName}");
}
}
return installedFontsCount;
}

Related

vb.net running a exe in memory

I'm trying to run a app on memory but I'm having error at Invoke. What am I doing wrong?
I'm trying to do this but in vb.net
Code C#
// read the bytes from the application exe file
FileStream fs = new FileStream(filePath, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
fs.Close();
br.Close();
// load the bytes into Assembly
Assembly a = Assembly.Load(bin);
// search for the Entry Point
MethodInfo method = a.EntryPoint;
if (method != null)
{
// create an istance of the Startup form Main method
object o = a.CreateInstance(method.Name);
// invoke the application starting point
method.Invoke(o, null);
}
in vb:
Dim instance As FileStream = File.Open("teste.exe", FileMode.Open)
Dim br As New BinaryReader(instance)
Dim bin As Byte() = br.ReadBytes(Convert.ToInt32(instance.Length))
instance.Close()
br.Close()
Dim a As Assembly = Assembly.Load(bin)
Dim metodo As MethodInfo = a.EntryPoint
If (IsDBNull(metodo) = False) Then
'create an istance of the Startup form Main method
Dim o As Object = a.CreateInstance(metodo.Name)
'invoke the application starting point
metodo.Invoke(o, Nothing)
Else
MessageBox.Show("Nao encontrado")
End If
UPDATE:
I found the answer. I created the "test.exe" like a ConsoleAplication, than in the module I code
Imports System.Windows.Forms
Module Module1
Sub Main()
Dim Form1 As New Form1
Form1.Show()
Do Until Form1.Visible = False
Application.DoEvents()
Loop
End Sub
End Module
Then I changed from ConsoleApplication to Windowsform And create my Form1. And this
metodo.Invoke(o, Nothing)
to this:
metodo.Invoke(Nothing, New Object() {})
Thank you guys for the support!
The Main method expects that you pass it the args parameter. Your current call is not passing any parameters, so I'm expecting that you are getting the following error if it ever reaches that line:
System.Reflection.TargetParameterCountException: Parameter count mismatch.
To fix it, just pass a one-element object array as the second parameter of the method.Invoke. Also, because the Main method is a static method, you don't need to do a CreateInstance before invoking the method.
So all you need is this:
metodo.Invoke(Nothing, New Object() {Nothing})
If, for some reason, you actually need pass values to the main's args parameter, you can do it like this:
metodo.Invoke(Nothing, New Object() {New String() {"param1", "param2"}})
Assuming the c# code works you mistranslated the null check.
The equivalent of 'if (method != null)' in vb.net is
If method IsNot Nothing Then
Dim o = a.CreateInstance(method.Name)
method.Invoke(o, Nothing)
End If

VB.net function that returns an object that is specified by the input

I am trying to write a function that returns a newly created object ( a form ) that is specified by the input. I'm having trouble with how to work out the concept of giving a type as an input then creating an object of that type in the body of the function. Here is an outline of what I'm working on.
Public Function MakeMyForm(ByVal frmType as Form) as Form
Dim NewObj as New frmType
Return NewObj
End Function
I'd like to be able to call the function in this way:
Dim myform as CustomFormType
myform = MakeMyForm(CustomFormType)
Can my concept be accomplished in VB.net?
Ok, if I understand you, you just want a generic method:
Public Function MakeMyForm(Of T As {New, Form})() As T
Return New T()
End Function
and call it like this:
Dim myform As CustomFormType = MakeMyForm(Of CustomFormType)()
of course, why wouldn't you just use:
Dim myform As New CustomFormType()
Well you can try this:
Dim frmnew() As Form
Dim createdforms As Integer = 0
Private Sub createform(wintext As String, height As Integer, width As Integer, backcolor As Color, topmost As Boolean, formborderstyle As FormBorderStyle, winstate As FormWindowState, opacity As Decimal, startposition As FormStartPosition, enabled As Boolean) 'add as many properties as you like
ReDim Preserve frmnew(createdforms)
frmnew(createdforms) = New Form
With frmnew(createdforms)
.Text = wintext
.Height = height
.Width = width
.BackColor = backcolor
.TopMost = topmost
.FormBorderStyle = formborderstyle
.WindowState = winstate
.Opacity = opacity
.StartPosition = startposition
.Enabled = enabled
End With
frmnew(createdforms).Show()
createdforms += 1
End Sub
and you can test it with the code below:
createform("Afnan Makhdoom", 500, 700, Color.Aqua, False, Windows.Forms.FormBorderStyle.Fixed3D, FormWindowState.Normal, 0.9, FormStartPosition.CenterScreen, True)
Public Function Makemyform(ByVal frmType As Form) As Form
Dim obj As Form
obj = newfunc(frmType)
Return obj
End Function
Public Function newfunc(ByVal mytype As Form) As Form
Return New Form
End Function
This is usually done using generics in a function such as:
Public Function GetItem(Of T)(key As String) As T
Usage:
myIntVar = myFoo.GetItem(Of Int32)(bar)
The purpose of which is for the code calling it to specify how it needs the return. In the above a whole bunch of data has been serialized and the original Type lost, so when fetching it back, the Of T helps convert it rather than using Object as the return.
For forms, it is more problematic:
Public Function MakeAForm(Of T)() As Form ' cant do As T
You'd have to add more code to cast Form to Form1 or frmCust to avoid tbName is not a member of System.Windows.Forms.Form errors. Even the correct way as shown by Mr Dokjnas present problems trying to do more with the form:
Public Function MakeAForm(Of T As {New, Form})() As T
Dim frm As New T
If frm.GetType Is frm8088.GetType Then
frm.textbox1.text = "ziggy" ' error
End If
Return frm
Here, it is 'TextBox is not a member of T`. If your forms were compiled to a ClassLib so the IDE could know more about the Types (forms) you could get it to work. But the first sign of futility is revealed in using it:
Dim frm As Form = MakeAForm(Of frm8100VI)()
frm.Show()
It takes more code to call the FormMaker than to just create an instance.

Get Result of Private Property TcpClient.BeginConnect, IAsyncResult in VB.NET

I have an application in VB.NET When I run the application in Visual Studio 2010 and mouseover an IAsyncResult, I see the protected property Result. I would like to read the value of the property in the application. How can I do that?
Imports System.Net
Imports System.Net.Sockets
...
Friend Function StartSendGo() As String
'Declarations
Dim strSendMachineName As String = "DEV001"
Dim intSendPort As Integer = 50035
Dim socketclient As New System.Net.Sockets.TcpClient()
Dim rslt As IAsyncResult = tcpClient.BeginConnect(strSendMachineName, intSendPort, New AsyncCallback(AddressOf ConnectCallback), socketclient)
Dim blnSuccess = rslt.AsyncWaitHandle.WaitOne(intTimeOutConnect, True)
'HERE is where I need rslt.Result.Message
End Function
Public Function ConnectCallback()
'Placeholder
End Function
When I mouseover rslt, VS shows that it is of type
System.Net.Sockets.Socket+MultipleAddressConnectAsyncResult I have never seen a plus (+) in a type before, and I am not able to declare a variable of that type. If I expand the properties, there is a protected property Result, which has a property Message with a value of "No connection could be made because the target machine actively refused it 192.0.0.10:50035". I need access to that message. I would also like to access addresses, but that is less important.
I found a solution - to use Reflection to read the value of the private property.
'Imports
Imports System.Reflection
'Call functions that write to rslt
rslt = tcpClient.BeginConnect(strSendMachineName, intSendPort, New AsyncCallback(AddressOf ConnectCallback), socketclient)
blnSuccess = rslt.AsyncWaitHandle.WaitOne(intTimeOutConnect, True)
'Use Reflection
'Get Type
Dim myType As Type = rslt.GetType()
'Get properties
Dim myPropertyInfo As PropertyInfo() = myType.GetProperties((BindingFlags.NonPublic Or BindingFlags.Instance))
'The order of the properties is not guaranteed. Find by name.
For Each pi As PropertyInfo In myPropertyInfo
If pi.Name = "Result" Then
'TODO Add check for nothing.
'Assign to Exception-type variable.
exException = pi.GetValue(rslt, Nothing)
End If
Next

.Net Dynamically Load DLL

I am trying to write some code that will allow me to dynamically load DLLs into my application, depending on an application setting. The idea is that the database to be accessed is set in the application settings and then this loads the appropriate DLL and assigns it to an instance of an interface for my application to access.
This is my code at the moment:
Dim SQLDataSource As ICRDataLayer
Dim ass As Assembly = Assembly. _
LoadFrom("M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\SQLServer.dll")
Dim obj As Object = ass.CreateInstance(GetType(ICRDataLayer).ToString, True)
SQLDataSource = DirectCast(obj, ICRDataLayer)
MsgBox(SQLDataSource.ModuleName & vbNewLine & SQLDataSource.ModuleDescription)
I have my interface (ICRDataLayer) and the SQLServer.dll contains an implementation of this interface. I just want to load the assembly and assign it to the SQLDataSource object.
The above code just doesn't work. There are no exceptions thrown, even the Msgbox doesn't appear.
I would've expected at least the messagebox appearing with nothing in it, but even this doesn't happen!
Is there a way to determine if the loaded assembly implements a specific interface. I tried the below but this also doesn't seem to do anything!
For Each loadedType As Type In ass.GetTypes
If GetType(ICRDataLayer).IsAssignableFrom(loadedType) Then
Dim obj1 As Object = ass.CreateInstance(GetType(ICRDataLayer).ToString, True)
SQLDataSource = DirectCast(obj1, ICRDataLayer)
End If
Next
EDIT: New code from Vlad's examples:
Module CRDataLayerFactory
Sub New()
End Sub
' class name is a contract,
' should be the same for all plugins
Private Function Create() As ICRDataLayer
Return New SQLServer()
End Function
End Module
Above is Module in each DLL, converted from Vlad's C# example.
Below is my code to bring in the DLL:
Dim SQLDataSource As ICRDataLayer
Dim ass As Assembly = Assembly. _
LoadFrom("M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\SQLServer.dll")
Dim factory As Object = ass.CreateInstance("CRDataLayerFactory", True)
Dim t As Type = factory.GetType
Dim method As MethodInfo = t.GetMethod("Create")
Dim obj As Object = method.Invoke(factory, Nothing)
SQLDataSource = DirectCast(obj, ICRDataLayer)
EDIT: Implementation based on Paul Kohler's code
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As System.Type
For Each assemblyType In Assembly.LoadFrom(file).GetTypes
Dim s As System.Type() = assemblyType.GetInterfaces
For Each ty As System.Type In s
If ty.Name.Contains("ICRDataLayer") Then
MsgBox(ty.Name)
plugin = DirectCast(Activator.CreateInstance(assemblyType), ICRDataLayer)
MessageBox.Show(plugin.ModuleName)
End If
Next
I get the following error with this code:
Unable to cast object of type 'SQLServer.CRDataSource.SQLServer' to type 'DynamicAssemblyLoading.ICRDataLayer'.
The actual DLL is in a different project called SQLServer in the same solution as my implementation code. CRDataSource is a namespace and SQLServer is the actual class name of the DLL.
The SQLServer class implements ICRDataLayer, so I don't understand why it wouldn't be able to cast it.
Is the naming significant here, I wouldn't have thought it would be.
Final Working code
Contents of PluginUtility:
enter code here Public Shared Function GetInstances1(Of Type)(ByVal baseDir As String, ByVal searchPattern As String) As System.Type()
Dim tmpInstances As New List(Of Type)
Try
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As System.Type
For Each assemblyType In Assembly.LoadFrom(file).GetTypes
Dim s As System.Type() = assemblyType.GetInterfaces
Return s.ToArray()
Next
Next
Catch exp As TargetInvocationException
If (Not exp.InnerException Is Nothing) Then
Throw exp.InnerException
End If
End Try
End Function
Code to load the DLL:
enter code here
Dim basedir As String = "M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\"
Dim searchPattern As String = "*SQL*.dll"
Dim plugin As CRDataLayer.ICRDataLayer
Try
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As System.Type
For Each assemblyType In Assembly.LoadFrom(file).GetExportedTypes
If assemblyType.GetInterface("CRDataLayer.ICRDataLayer") IsNot Nothing Then
plugin = DirectCast(Activator.CreateInstance(assemblyType), CRDataLayer.ICRDataLayer)
MessageBox.Show(plugin.ModuleDescription)
End If
Next
Next
Catch exp As TargetInvocationException
If (Not exp.InnerException Is Nothing) Then
Throw exp.InnerException
End If
Catch ex As Exception
MsgBox(ex.Message)
Clipboard.SetText(ex.Message)
End Try
Version 2 - This sample loads up a DLL from it current directory.
There are 2 projects, 1 console application project and a "module" project (the module 'coppies' its DLL to the working directory of the console app).
The sample below simply demonstrates dynamically loading a DLL that implements an interface. The IModule interface just reports its name. PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll") will create an instance of any IModule instance found in a DLL within the current directory ending with ".Module.dll". It's a reflected VB.NET version straight out of Mini SQL Query.
With that in mind something like:
Dim modules As IModule() = PlugInUtility.GetInstances(Of ICRDataLayer)(Environment.CurrentDirectory, "*.Server.dll")
Should satisfy your requirement. Then you just need to chose which one to execute!
The code:
In "VB.LoaderDemo Colsole App"
' IModule.vb
Public Interface IModule
Property ModuleName() As String
End Interface
' PlugInUtility.vb
Imports System.IO
Imports System.Reflection
Public Class PlugInUtility
Public Shared Function GetInstances(Of T)(ByVal baseDir As String, ByVal searchPattern As String) As T()
Dim tmpInstances As New List(Of T)
Try
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As Type
For Each assemblyType In Assembly.LoadFrom(file).GetTypes()
If (Not assemblyType.GetInterface(GetType(T).FullName) Is Nothing) Then
tmpInstances.Add(DirectCast(Activator.CreateInstance(assemblyType), T))
End If
Next
Next
Catch exp As TargetInvocationException
If (Not exp.InnerException Is Nothing) Then
Throw exp.InnerException
End If
End Try
Return tmpInstances.ToArray()
End Function
End Class
' MainModule.vb
Module MainModule
Sub Main()
Dim plugins As IModule() = PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll")
Dim m As IModule
For Each m In plugins
Console.WriteLine(m.ModuleName)
Next
End Sub
End Module
In "Sample1 DLL" (references 'VB.LoaderDemo' for IModule)
Imports VB.LoaderDemo
Public Class MyModule1
Implements IModule
Dim _name As String
Public Sub New()
_name = "Sample 1, Module 1"
End Sub
Public Property ModuleName() As String Implements IModule.ModuleName
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class
The output is:
> Sample 1, Module 1
Is the type ICRDataLayer defined in the DLL you are going to load? If so, you seem to already reference the DLL in your project settings.
You need to work with just reflection:
Dim obj As Object = ass.CreateInstance("ICRDataLayer", True)
Dim t as Type = obj.GetType()
Dim method as MethodInfo = t.GetMethod("DoSomething")
method.Invoke(obj, ...)
Edit: If ICRDataLayer is implemented in the application, and the plugin just implements the interface, you need the plugin to provide a factory for you: (sorry for C# code, I am not familiar with VB.NET's syntax)
// in each of plugins:
static class CRDataLayerFactory // class name is a contract,
{ // should be the same for all plugins
static ICRDataLayer Create()
{
return new CRDataLayerImplementation();
}
}
The application's code should look like this:
Dim factory As Object = ass.CreateInstance("CRDataLayerFactory", True)
Dim t as Type = factory.GetType()
Dim method as MethodInfo = t.GetMethod("Create")
Dim obj as Object = method.Invoke(factory, null)
SQLDataSource = DirectCast(obj, ICRDataLayer)
A few things to look for in your code
Debug through and check that the
assembly is loaded correctly, in case
it fails due to dependency checking
Instead of using GetType, use GetExportedType so you have smaller subset to iterate through
The CreateInstance should use your loadedType rather than the interface (you cant create object from an interface)
Personally, I dont like naming my variable ass, I would shorten it to assem instead :)

How can I specify a printer other than the default printer?

I'd like to be able to specify two different printers for two different jobs. I'm using the following class to handle printing these, but regardless of what I do, the default printer is always the one that's printed to.
Public Class Receipt : Inherits Printing.PrintDocument
Private _font As Font = New Font("Courier", 8)
Private _text As String = ""
Public Property Text() As String
Get
Return _text
End Get
Set(ByVal Value As String)
_text = Value.Trim
End Set
End Property
Public Sub New(ByVal str As String, ByVal settings As Printing.PrinterSettings)
MyBase.New()
_text = str
Me.PrinterSettings = settings
End Sub
Protected Overrides Sub OnPrintPage(ByVal e As Printing.PrintPageEventArgs)
Dim printHeight As Integer
Dim printWidth As Integer
Dim leftMargin As Integer
Dim rightMargin As Integer
With Me.DefaultPageSettings
.PaperSize = New System.Drawing.Printing.PaperSize("Custom", 300, 1200)
.Margins.Left = 25
.Margins.Right = 25
printHeight = .PaperSize.Height - .Margins.Top - .Margins.Bottom
printWidth = .PaperSize.Width - .Margins.Left - .Margins.Right
leftMargin = .Margins.Left
rightMargin = .Margins.Top
End With
Dim printArea As New RectangleF(leftMargin, rightMargin, printWidth, printHeight)
Dim format As New StringFormat(StringFormatFlags.LineLimit)
Try
e.Graphics.DrawString(_text, _font, Brushes.Black, printArea, format)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
End Class
If I inspect my PrinterSettings attribute immediately before the call to DrawString, the PrinterName attribute is still correctly set to the printer I specify, but it's still the default printer that kicks out the job. I'm sure I'm missing something obvious, but would certainly appreciate if someone could point out what it is. :)
Thanks
I just created a test app with the class code you posted and it works fine. It uses whatever printer I select. So I must conclude that wherever you are using this class you're accidentally altering the PrintSettings object after you initialize the object but before you call Print.
Or perhaps the printer name you specify isn't valid and the default is used as a backup. You can check this using PrinterSettings.IsValid after setting the PrinterName property.
The PrinterSettings.PrinterName property is actually what you should be using.
You can get a list of installed printers using PrinterSettings.InstalledPrinters (System.Drawing.Printing namespace). Perhaps your provider printer name is slightly different from what it should be, because I can confirm this actually works.