I am working on creating a short game in which the user moves a picturebox across the form and works toward a set destination. I have been able to change the direction that my character faces on the KeyPress event; however, I am unable to use my timer to animate this picturebox via the changing of an integer variable, and as I am already working inside of the loop that is the timer, I cannot create another loop. As I am a novice programmer, as in extremely new to any coding whatsoever, the method of tracking what picture is currently set in the picturebox is unknown to me, and I have researched it to no avail.
This is how I change the image that initially displays in the picturebox on KeyPress and move the picturebox:
If aKey or leftKey Then
picPlayer.BackgroundImage = My.Resources.PlayerRightStand
'If this is an okay movement, then move
If picPlayer.Left -= speed >= 0 Then
picPlayer.Left -= speed
End If
End If
I have tried this to track what image is currently set as the background of picPlayer:
If picPlayer.BackgroundImage Is My.Resources.playerRightStand Then
image = 1
...etc., but I think there is a gap in my knowledge or a flaw in my logic because I haven't been able to make this work. If anyone can help with this, which I'm sure actually has a very simple answer, I would greatly appreciate your time and answer. Thanks!
What you're trying can't possibly work because My.Resources works by creating a new object every time you access it. That means that even My.Resources.PlayerRightStand Is My.Resources.PlayerRightStand would evaluate to False.
That means that, even your specific issue aside, you should generally not use My.Resources in your general code, but rather assign directly from My.Resources to a field and then use that field in your code, e.g.
Private playerRightStand As Image = My.Resources.PlayerRightStand
That way, you ensure that you only extract that data once and it also means that you can compare the Image property of your PictureBox to that field at any time and get the expected result. So:
If aKey or leftKey Then
picPlayer.BackgroundImage = playerRightStand
'If this is an okay movement, then move
If picPlayer.Left -= speed >= 0 Then
picPlayer.Left -= speed
End If
End If
and:
If picPlayer.BackgroundImage Is playerRightStand Then
image = 1
You might also consider creating an array and putting all your Images in that. You can then use Array.IndexOf to get the index of any specific Image and you might also store the index of the current Image in a field, meaning that you wouldn't have to keep comparing Image references.
I think you should use a variable to store the current status instead of tracking the image. This has a lot of advantages:
You can track a lot of status, even those who are not related to a specific image/animation
You can use the same image for different status
You can code the logic without having all the images/animation, and implement the graphic later
You can change the graphic resources, the images, the names without changing the logic
Since you also have different frames, you may store Arrays as jmcilhinney suggested.
You could use an enum for that:
Public Enum Status
rightStand
leftStand
jumping
crouching
NONE
End Enum
You can store the previous status and the new one (useful to know if you need to change the image, or loop through animation):
Public previousStatus As Status = Status.NONE
Public newStatus As Status = Status.NONE
Then you have the status changing logic (just and example):
Public Sub changeStatus()
If leftKey Then
newStatus = Status.leftStand
ElseIf rightKey Then
newStatus = Status.rightStand
End If
End Sub
And the image modification logic (you'll need more work on this part if you want animations loop):
Public Sub applyImage()
If previousStatus = newStatus Then Exit Sub
Select Case New Status
Case Status.crouching
picPlayer.BackgroundImage = playerCrouching
Case Status.jumping
picPlayer.BackgroundImage = playerJumping
Case Status.leftStand
picPlayer.BackgroundImage = playerLeftStand
Case Status.rightStand
picPlayer.BackgroundImage = playerRightStand
End Select
End Sub
Note: This question is created to help others find what I could not find, having said that anyone is welcome to propose a better solution than I came up with.
My challenge was to produce a smooth scrolling text stream on a windows form application, in addition the form had other items drawn on it so I had to avoid destroying them in the process.
I tried moving a text box incrementing it with timers and do...loops and so on, also tried using Graphics.DrawString again incrementing the position.
All of them produced a jerky output with the text flashing as it moved.
I Spent several hours googling and browsing probably in the region of 50 different proposed solutions including many in other languages in the hope I might get a clue!
I can't identify which one gave me the clue but I saw several references to Doublebuffer so I googled that (and tried it without sucess!) and finally came up with the idea from here of a manual buffer so I ended up with....(see answer below)
Do
Dim currentContext As BufferedGraphicsContext
Dim myBuffer As BufferedGraphics
' Gets a reference to the current BufferedGraphicsContext.
currentContext = BufferedGraphicsManager.Current
' Creates a BufferedGraphics instance associated with Form1, and with
' dimensions the same size as the drawing surface of Form1.
myBuffer = currentContext.Allocate(BNP1.CreateGraphics, BNP1.DisplayRectangle)
'update test
With myBuffer
.Graphics.Clear(Panel.BackColor)
.Graphics.DrawString("Some Text", [Your Font], Brushes.Blue, xpos, ypos)
End With
myBuffer.Render()
myBuffer.Dispose()
'This bit will need adjusting to suit your layout!
If xpos + Panel.Width < Panel.Left Then xpos = Panel.Right Else xpos -= 1
Thread.Sleep(1)
Loop
This produced a perfectly smooth scroll with a fully adjustable scroll speed using the sleep time and xpos reduction.
As you will probably notice I did this with a Do...Loop in a separate thread it also works in a timer but the scroll speed for some reason (someone can probably explain) is much slower.
Disclaimer: I am not a professional coder so if anyone has suggestions to improve what I have done I will greatly appreciate it (avoid generics such as Option Strict - already on!)
is there any way to make this work faster?
Here is my sample code in vb.net. This adds a point on a chart at mouse position but it is quite slow.
Private Sub Chart2_MouseMove(sender As Object, e As MouseEventArgs) Handles Chart2.MouseMove
Dim coord() As Double = GetAxisValuesFromMouse(e.X, e.Y)
Dim test As Series
Try
Chart2.Series.RemoveAt(1)
Catch ex As Exception
End Try
Dim pt As New DataPoint
pt.XValue = coord(0)
pt.YValues(0) = coord(1)
test = New Series
Chart2.Series.Add(test)
Chart2.Series(test.Name).ChartType = SeriesChartType.Point
Chart2.Series(test.Name).Points.Add(pt)
End Sub
Function returns the coordinates of x and y axis at mouse position.
Private Function GetAxisValuesFromMouse(x As Integer, y As Integer) As Double()
Dim coord(1) As Double
Dim chartArea = Chart2.ChartAreas(0)
coord(0) = chartArea.AxisX.PixelPositionToValue(x)
coord(1) = chartArea.AxisY.PixelPositionToValue(y)
Return coord
End Function
Result:
In your Chart2 window, there's got to be a way to perform an Invalidate with a clipping rectangle.
Another method I use is not to paint directly to the window, but instead paint to a memory bitmap, which I then BLT to the visible window. That can actually be faster because in painting to the bitmap it's not having to slow down to do clipping. This also gives a (fake) impression of speed because I can't see the green lines being redrawn (even though they are).
Yet another method is, when painting the dot where the mouse is, XOR it to the screen.
Then when I move it, XOR it in the old location (to erase it), and then XOR it in the new location. That way, I'm not repainting all those little green lines that haven't moved.
Still another method is: if there are thousands of little lines in the graph, some of them have to be of length zero pixels. They take time to draw even though they contribute nothing to the image, so they could be omitted.
Use Flexcell Grid control
it is superb control it provide all those feature which provide by datagrid control and more
FlexCell is a flexible and easy to use grid control. ... It provides comprehensive functions such as Print, Print Preview, Chart, ... Using FlexCell Grid Control, you can create the professional user interfaces and report form in your application. ... your applications coded in Visual Basic, Visual FoxPro, Visual C++, VB.net, C#, etc.
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
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.