Currently, I have a UI where the user chooses a product from a list. When they double click on the product of their choice another form shows the product with some additional information as well as the associated bar code. On this second form there is a print button. (This is where my question is) When the print button is clicked I want send this information to a Label Template and viewable (probably though a PrintPage) and then be able to be sent to a printer from that.
What is the best way to accomplish this? I can think of two different ways to accomplish this but I'm sure there are better ways out there.
1) I can create the template in XAML and then databind to the form
2) I can create the template in the form.vb 's _PrintPage event. (most straightforward but not the fastest)
Is there an easier way to create the template and send the information to it and then print the template?
It would be easier to just make another windows forms and design the template that way, but is it possible to send this form to a print object at runtime?
Typically anything I have to print, like labels, I use a reporting tool. .Net comes with its own Microsoft Reporting Tool, http://msdn.microsoft.com/en-us/library/bb885185.aspx, and there are lots of third party report tools like Crystal or Telerik. They all have visual designers for layout and handle all the print preview and printing stuff for you.
If I understand you correctly you want to control the process within your own program without using report tools? In that case I will list some thoughts about one implementation -
You can make a simple template functionality using (a serializable) dictionary that contains regions and region types. As you work with an absolute area (the label itself) you can for example do this:
Private Template as Dictionary = New dictionary(Of String, TemplateEntry)
Friend Class TemplateEntry
Public MeasureType As MeasureType
Public RegionType As Regiontype
Public X As Single
Public Y As Single
Public Width As Single
Public Height As Single
Public Content as Object
End Class
Friend Enum MeasureType
Centimeters
Millimeters
Inches
Pixels
'...
End Enum
Friend Enum RegionType
Text
[Image]
BarCode
'...
End Enum
Then in you code you convert the x,y,w,h into pixels based on the DPI and return a Rectangle for example. You can XML Serialize the dictionary to create other templates and so forth.
The Content field is initialized when parsed.
To add you could do for example:
Dim tmpl As TemplateEntry = New TemplateEntry
tmpl.MeasureType = MeasureType.Millimeters
'...
Template.Add("Barcode", tmpl)
Then "render" the template into your canvas for printing:
For Each tLine in Template
Dim r as rectangle = CalcMeasuresIntoPixels(tmpl)
'...
' render each element based on type and location
Next
Hope this gave some input.
Related
I am trying to change the Image attribute associated with a button when the button is clicked.
I have added the image I want to display (called "Black Pawn.png") as a resource within the solution and (from searching around and looking at similar questions) am trying to set the image attribute to it as shown below:
Private Sub boardButtonA2_Click(sender As Object, e As EventArgs) Handles boardButtonA2.Click
Dim sprites As Object = My.Resources.ResourceManager
boardButtonA2.Image = sprites.GetObject("Black Pawn.png")
End Sub
But when I click the button, the default image on it just disappears and is replaced with nothing instead of Black Pawn.png.
I am pretty new to using Visual Basic and Visual Studio so I may have failed to have added the image as a resource properly but I can see the image in the Solution Explorer above the form the button is in.
Any help or advice would be a great help.
Thanks.
EDIT:
Having thought more about the potential scenario, I'm wondering whether this answer is actually relevant. Your example code uses a hard-coded String but I'm wondering whether the actual String really would be a user selection. I'll leave it here anyway.
ORIGINAL:
There's no reason to use a hard-coded String to get a resource. If the String was from user entry then maybe, depending on the circumstances. As it is though, you should be using the dedicated property for that resource. That means using this:
boardButtonA2.Image = My.Resources.Black_Pawn
Just be aware that a new object is created every time you get a resource from My.Resources. For that reason, don't keep getting the same property over and over. If you need to use a resource multiple times, get it once and assign it to a field, then use that field multiple times, e.g.
Private blackPawnImage As Image
Private Function GetBlackPawnImage() As Image
If blackPawnImage Is Nothing Then
blackPawnImage = My.Resources.Black_Pawn
End If
Return blackPawnImage
End Function
and then:
boardButtonA2.Image = GetBlackPawnImage()
Also, I suggest that you change the name of the property to BlackPawn rather than Black_Pawn. You can change it to whatever you want on the Resources page of the project properties.
EDIT:
If this application is a chess game then you definitely will need every resource image for the playing pieces so you probably ought to get all of them at load and assign them to variables, then use them from those variables over the course of the game.
(I am assuming the question is for WinForms, if it isn't I will remove this answer)
When adding images as resources to a project, you have to pay attention to the name given to the resource after it is imported - the name can be changed if you want.
The image below is from one of my projects:
The name of the files on disk are:
CSV - Excel.png
GreenDot.png
RedDot.png
To fix your problem change the line:
boardButtonA2.Image = sprites.GetObject("Black Pawn.png")
to be:
boardButtonA2.Image = sprites.GetObject("Black_Pawn")
I don't think adding the image by going into Project > Add Existing Item properly added the image as a resource.
I instead went into *Project > (Solution Name) Properties > Resources > Images > Add Existing Item * and added it that way and was then able to get it working using jmcilhinney's method (I think my original method/a variation of it would work too but theirs is better).
First things first. There's a good chance what I want to do should really be done with VB and not VBA. But as long as it is possible I would rather use VBA.
I have a userform of essentially a big diagram made of hundreds of labels. I want to separate these labels into groups. And then separate these groups into subsystems. The idea being I have some form of heirarchy to work with. The label groups need to change color based on what I have selected in a combo box, and if I click on one of these labels I want to bring up a user form showing details of the subsystem using click events.
I'm pretty sure I need to use multiple classes to do what I want but am fairly new to using class modules. Though I get the concept.
Basically I want some functionality that goes subsystem -> label group( or part) -> color with click events for the whole subsystem and combo box events for changing label group colors.
I saw a thread online about grouping labels or text boxes but it only works to trigger the even for a group, not change the properties of the whole group once the event is triggered. I would like to set this up in classes as well so I can export the system for use in other future userforms.
I was able to create groups of labels and change them together like I wanted:
CPart (Class Module 1):
*This is meant to handle the event triggering of the labels and includes some color code that I used to test functionality of the groups changing together and functionality of changing colors.
Public WithEvents trigger As MSForms.Label
Dim pLabels As Collection
Property Set triggers(c As Collection)
Set pLabels = c
End Property
Private Sub trigger_Click()
For Each obj In pLabels
obj.BackColor = RGB(255, 0, 0)
Next obj
End Sub
CTrigger (Class Module 2):
*This took a collection of labels which were passed in through a collection variable in the userform and then stored each label as a trigger in a separate class variable, along with the whole collection of labels in that group. This way when any trigger fires the event, all of the labels change.
Dim CTrigger() As New CPart
Dim pLabels As Collection
Dim i As Integer
Property Set Labels(c As Collection)
Set pLabels = c
For i = 1 To pLabels.Count
ReDim Preserve CTrigger(1 To i)
Set CTrigger(i).trigger = pLabels.Item(i)
Set CTrigger(i).triggers = pLabels
Next i
End Property
Property Get Labels() As Collection
Labels = pLabels
End Property
I really don't like the way it works, partly because I am losing myself in the logic of it constantly, and partly because it means that in order to use this I have to make collections of labels in the userform module anyway just to run it. It is very inefficient code, but I am putting it up so you get an idea of what I am trying to accomplish.
What I would much rather do instead is have one class variable to hold my custom collection of labels (a "LabelGroup"). Another class variable is likely required to hold the labels themselves (I think). And then all I would have to do is go through and write methods for the LabelGroup class such as changecolor, and it could handle that. But I can handle that part, for now what I really need help with is setting up the class framework in a neat way, so that the module I will eventually run could just say things like:
LabelGroup1.Add Label1
LabelGroup2.Add Label2
or
Private Sub button_click()
LabelGroup1.ChangeColor(RGB(...))
End Sub
These two articles have been helping me along:
http://www.databaseadvisors.com/newsletters/newsletter200503/0503usingcustomcollections/using%20custom%20collections%20in%20microsoft%20access.asp
http://j-walk.com/ss/excel/tips/tip44.htm
I was just looking at something similar but not quite so detailed. I'm trying to improve the look of a complex userform by making it look more modern and was going to try to fake mouseOver highlighting or at least active/inactive shading for labels placed overtop of graphical buttons.
Anyway, have you considered just changing the names of the label objects so that they are prefixed/suffixed with some kind of group or subsystem ID?
That way when you pass them to a sub to change their colour, you can check the prefix or suffix.
I'm looking for an option with which I can use the functionality of a combobox together with a
listview.
The reason for this is because I have SQL queries which can't have more than 1 output, which I can get in some cases.
E.g. my SQL table looks somewhat like this
Unique_ID - Name
123456789 - Simon
987654321 - Simon
Basically the same name can be in the database multiple times, each entry with it's own ID.
For the user's sake, I can't have them choose what records to edit based on the ID, instead I have them base the chosen record on the name. When there's more than 1 record resulting from my query though, I get a MySQL exception.
Here's the MySQL query in question:
"SELECT worldspace from survivor where is_dead = '0' _ and survivor.unique_id = (select unique_id from profile where name = '" & target & "')"
The output from that query is then used in another UPDATE query.
So, is it possible for my combobox to have both the ID & the name as values, with a clear seperation between them so the entire value stays well readable, as it would do in a listview element?
I see you've already gotten the HighCore treatment about how easy everything is in WPF and how much WinForms sucks. But you might be interested to know that you can do this in WinForms, too. You just do it a little bit differently. It should come as no surprise that the standard design idioms differ in WinForms and WPF; that doesn't justify one being "better" than the other, it just means you need to learn how to use the one you're using. (Although, admittedly, some of the fancier stuff is a bit more difficult to achieve using a UI framework that was invented 20 years ago with Windows itself. The power it does have is rather remarkable.)
There are two basic ways of formatting the information: everything on a single line (which I believe is what you asked for in the question) or the pieces of information on two lines where each item is basically a two-line unit (which is what HighCore's WPF solution demonstrates).
Single-Line Format
The Simplistic Approach
We'll look at putting everything on a single line first, which really is simple. You don't need columns for separation, you can just use some kind of distinctive separator character when you add the items to the combobox, such as a vertical pipe (|) or a dash (-) like you used in the question.
This works so well because the ComboBox.Items.Add method accepts a parameter of type Object, on which it just calls ToString to get the value displayed in the control. If you pass it a string, it displays that string.
myComboBox.BeginUpdate()
For Each record In myRecordSet
myComboBox.Items.Add(String.Format("{0} | {1}", record.UniqueID, record.Name))
' or even...
myComboBox.Items.Add(String.Format("{0} ({1})", record.UniqueID, record.Name))
Next record
myComboBox.EndUpdate()
OR
An Incremental Improvement Through OOP
You can even pass a custom class to the Add method that keeps track of the unique ID and name properties (and anything else you want) and overrides the ToString method for display purposes.
Public Class Record
Public Property UniqueID As Long ' maybe this should be a string too
Public Property Name As String
Public Overrides Function ToString() As String
' Generate the string that will be displayed in the combobox for this
' record, just like we did above when adding it directly to the combobox,
' except that in this case, it will be dynamically generated on the fly,
' allowing you to also track state information along with each item.
Return String.Format("{0} | {1}", Me.UniqueID, Me.Name)
End Function
End Class
' ...
' (somewhere else, when you add the items to the combobox:)
myComboBox.BeginUpdate()
For Each r In myRecordSet
' Create a Record object representing this item, and set its properties.
Dim newRecord As New Record
newRecord.UniqueID = r.UniqueID
newRecord.Name = r.Name
' ...etc.
' Then, add that object to the combobox.
myComboBox.Items.Add(newRecord)
Next r
myComboBox.EndUpdate()
Fixing the Jaggies
Granted, if the first item in each set can be of variable length and you're using a variable-width font (i.e., one that is not monospaced like every UI on the planet does except code editors), the separators won't line up and you won't get two nicely-formatted columns. Instead, it looks all jumbled and ugly.
It would be nice of the ComboBox control supported tab characters that would handle lining everything up for us automatically, but unfortunately it does not. This is, regrettably, a hard limitation of the underlying Win32 control.
Fixing this ragged-edge problem is possible, but it does get a bit complicated. It requires taking over the drawing of the items in the combobox, referred to as "owner-draw".
To do this, you set its DrawMode property to OwnerDrawFixed and handle the DrawItem event to manually draw the text. You'll use the TextRenderer.DrawText method to draw the caption string (because that matches what WinForms uses internally; avoid using Graphics.DrawString), and TextRenderer.MeasureText if necessary to get the spacing right. The drawing code can (and should) use all of the default properties provided by the DrawItemEventArgs passed as e. You don't need OwnerDrawVariable mode or to handle the MeasureItem event because the width and height of each item cannot vary in this case.
Just to give you an idea, here's a quick-and-dirty implementation that simply divides the drop-down in half vertically:
Private Sub myComboBox_DrawItem(sender As Object, e As DrawItemEventArgs) Handles myComboBox.DrawItem
' Fill the background.
e.DrawBackground()
' Extract the Record object corresponding to the combobox item to be drawn.
Dim record As Record = DirectCast(myComboBox.Items(e.Index), Record)
Dim id As String = record.UniqueID.ToString()
Dim name As String = record.Name
' Calculate important positions based on the area of the drop-down box.
Dim xLeft As Integer = e.Bounds.Location.X
Dim xRight As Integer = xLeft + e.Bounds.Width
Dim xMid As Integer = (xRight - xLeft) / 2
Dim yTop As Integer = e.Bounds.Location.Y
Dim yBottom As Integer = yTop + e.Bounds.Height
' Draw the first (Unique ID) string in the first half.
TextRenderer.DrawText(e.Graphics, id, e.Font, New Point(xLeft, yTop), e.ForeColor)
' Draw the column separator line right down the middle.
e.Graphics.DrawLine(SystemPens.ButtonFace, xMid, yTop, xMid, yBottom)
' Draw the second (Name) string in the second half, adding a bit of padding.
TextRenderer.DrawText(e.Graphics, name, e.Font, New Point(xMid + 5, yTop), e.ForeColor, TextFormatFlags.Left)
' Finally, draw the focus rectangle.
e.DrawFocusRectangle()
End Sub
Now, this is looking pretty good. You can certainly improve on the technique used by the DrawItem event handler method, but it works out pretty well as is, so long as the combobox is made the right size for the values it will be displaying.
Multiple-Line Format
Defining a Custom ComboBox Class
The second method, where each item is a two-line group like HighCore's WPF example, is best done by subclassing the built-in ComboBox control and taking complete control its drawing routines. But that's nothing to be afraid of, subclassing a control is a standard WinForms idiom to gain extra control over the UI. (You could, of course, implement all of this by handling events like I did above, but I think subclassing is a much cleaner approach and also promotes reuse if you want to have multiple comboboxes that all behave in a similar fashion.)
Again, you don't need OwnerDrawVariable because the height of the items is not going to change. You'll always have two lines, so a fixed height works fine. You just need to make sure that you set the ItemHeight property to double of its normal value because you're going to have two lines. You could do this the complicated way using TextRenderer.MeasureText, or you could do it the easy way by just multiplying the default value by 2. I chose the latter for this demo.
Add this class into your project, and then use the MultiLineComboBoxcontrol instead of the built-in System.Windows.Forms.ComboBox. All of the properties and methods work the same.
Public Class MultiLineComboBox : Inherits ComboBox
Public Sub New()
' Call the base class.
MyBase.New()
' Typing a value into this combobox won't make sense, so make it impossible.
Me.DropDownStyle = ComboBoxStyle.DropDownList
' Set the height of each item to be twice its normal value
' (because we have two lines instead of one).
Me.ItemHeight *= 2
End Sub
Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
' Call the base class.
MyBase.OnDrawItem(e)
' Fill the background.
e.DrawBackground()
' Extract the Record object corresponding to the combobox item to be drawn.
If (e.Index >= 0) Then
Dim record As Record = DirectCast(Me.Items(e.Index), Record)
' Format the item's caption string.
Dim caption As String = String.Format("ID: {0}{1}Name: {2}", record.UniqueID.ToString(), Environment.NewLine, record.Name)
' And then draw that string, left-aligned and vertically centered.
TextRenderer.DrawText(e.Graphics, caption, e.Font, e.Bounds, e.ForeColor, TextFormatFlags.Left Or TextFormatFlags.VerticalCenter)
End If
' Finally, draw the focus rectangle.
e.DrawFocusRectangle()
End Sub
End Class
Adding Fancies and Flourishes
What we've got now isn't bad, but by lavishing a bit more effort on the drawing code in OnDrawItem, we can add some extra visual fancies and flourishes.
For example, without the selection rectangle, it would be pretty hard to tell that these are actually two-line units. That's unusual for a combobox control, so for usability reasons your application should go out of its way to make this abundantly clear. One way we might do that is by indenting the second line. You'll recall that I said that the built-in combobox control doesn't support tabs? Well that doesn't apply anymore, since we're doing the drawing ourselves now. We can emulate tabs by adding some extra padding to the beginning of the second line.
If you wanted the labels ("ID:" and "Name:") to be set apart from the actual values, you could do that, too. Perhaps you'd make the labels bold and lighten the text color.
So you see that just by playing with the drawing code, you can create almost any effect you want. We have complete control, and by wrapping it all up in a MultiLineComboBox class that can be reused all over the place, the rest of your code doesn't even have to know that anything special is happening. Cool, right?
Avoiding All the Work
And finally, I would be remiss if I didn't point out that you could skip doing all of this work and take your pick of the variety of custom multi-line combobox controls that have already been written.
This one is pretty nifty. It actually just displays a ListView control when you click the drop-down arrow on the combobox. In doing so, you get all of the formatting niceties of the ListView control for free, but it behaves just like a regular ComboBox.
This would be a WPF ComboBox with multiple Lines:
<ComboBox ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding ID, StringFormat='{}ID: {0}'}"/>
<TextBlock Text="{Binding Name, StringFormat='{}Name: {0}'}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Data Item:
public class ComboItem
{
public int ID { get; set; }
public string Name { get; set; }
}
Result:
To all the hackforms zombies: technical superiority speaks by itself.
The solution is actually way, way easier. Try this:
For i As Integer = 0 To dataSet.Tables(0).Rows.Count - 1
ComboBox1.Items.Add(dataSet.Tables(0).Rows(i)(0) + " | " + dataSet.Tables(0).Rows(i)(1)
+ " | " + dataSet.Tables(0).Rows(i)(2))
Next
But you have to implement it to your Code and DB
How do I serialize and deserialize my main form (and its controls, subcontrols, their values, etc.)?
Edit, for clarification. Currently I am writing the current value of each control to an .INI file, one by one and reading it back when the program is next run. Every time I add a new control, I have to remember to update that save/load .INI code.
I just wondered if I can do it in one call, or a simple for loop iterating over all controls on the form.
Btw, I use only simple controls like edit box, radio button, combo box, checkd listbox, the most complex thing I have is a datagrid, but even that is not linked to a databse.
Accepted answer "can't. I will probably code my own, along the lines of ...
for each child control (recursivley)
if conrol is editbox ...
if control is radiobutton ...
if ... etc
write control name to .ini file
write control "value" to .ini file
maybe later add left/top/height/width/enabled/visible, etc, bu tfor not th econtrol name an its "value" are enough (text, value, lines, checked?, etc)
There's no out-of-the-box support for serializing .NET forms and controls. Controls are not marked with the [Serializable] attribute.
I think most of the difficulty revolves around .NET controls that are really wrappers of native Win32 controls. Persisting the native state as XML, it seems, would be infeasible.
Perhaps someone has written a custom serializer; if not, you may need to roll your own.
Edit:
I found this discouraging accepted answer on experts exchange:
You have to implement ISerializable
or IXmlSerializable in order to do
something like that (depending on how
do you want to serialize the form).
It's not trivial.
Serializing the entire control sounds like a difficult proposition. However, if you want to serialize the data within that control, that's certainly possible, assuming you have it structured decently well.
For example, let's say you have a Person object with a List of Address for your AddressBook application:
Public Class Person
Public Property PersonName As String
Public Property PersonAge As Integer
Public Property Addresses As New List(Of Address)()
End Class
Public Class Address
Public Property StreetAddress As String
Public Property City As String
Public Property State As String
Public Property Zip As String
End Class
If you bind this data to your form, you can easily serialize and deserialize it to and from XML. To serialize:
Imports System.Xml.Serialization '<==you need to import this namespace '
'...'
Dim thisPerson As New Person()
Dim serializer = new XmlSerializer(thisPerson.GetType())
Using writer = XmlWriter.Create("thisPerson.xml"))
serializer.Serialize(writer, thisPerson)
End Using
To deserialize:
Dim thisPerson As New Person()
Dim serializer As New XmlSerializer(thisPerson.GetType())
Using reader = XmlReader.Create("thisPerson.xml")
thisPerson = CType(serializer.Deserialize(reader),Person)
End Using
I learned about XML Serialization from this answer to a question I had previously asked.
Granted, if you're manually loading/extracting data from your forms, this isn't going to work. I good idea might be to encapusate your underlying data for a form in a class and then bind that class to the form. This way, you can easily serialize/deserialize the data in the form.
My VB.Net Winforms app is a tool to allow hierarchical data to be edited in a tree, then stored in a database. I am using a treeview control.
Content is drag-dropped from other documents onto the treenodes, or the nodes can be edited directly.
if I edit the database field directly, and enter a bit of content (a thousand characters long or more!), the treeview will happily display it.. but, when I drag drop, the data is being truncated at 259 characters. If I edit directly, the maximum edit 'window' is also 259 characters.
259 seems like a really strange number to stop at, so I am wondering - where does this size come from, and can I change it programmatically?
I would recommend taking a different approach. You probably don't want to show your users all 10000 or characters of a document anyway in their TreeNode, so create an custom data storage class
with properties like Name and Content to store the document and it's title. Add your content to the Content property and a title or something meaningful to the Name property then add the object to the Tag property of the TreeNode object.
Dim mynode As New TreeNode
Dim SomeBigCustomObject as New MyContentStorageObject(name,content)
mynode.Text = SomeBigCustomObject.Name
mynode.Tag = SomeBigCustomObject
TreeView1.Nodes.Add(mynode)
You can then get the object back when a node is selected (using the AfterSelect event) like this:
dim ContentStorageObject As MyContentStorageObject = CType(e.Node.Tag, MyContentStorageObject)
dim content as string = ContentStorageObject.Content
If you need to edit the text, I would then either pop up a editor dialog or send the data that is stored in Content to an textbox on your form for editing. Your users will probably appreciate not having to type it all in the treeview node editor.
That's a real quick and dirty explanation, but the essence is "use the .Tag property". Good luck.