Virtual Battlemat
Virtual Battlemat
Virtual Battlemat
Virtual Battlemat
Virtual Battlemat
Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat
Virtual Battlemat Virtual Battlemat
Virtual Battlemat
Go Back  Xtreme Visual Basic Talk > > > Virtual Battlemat


Reply
 
Thread Tools Display Modes
  #1  
Old 10-24-2008, 10:14 PM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default Virtual Battlemat


...for games like D&D and Warhammer so I can display it using an LCD projector instead of a table with minis. The goal is something small and simple with a paintable surface, a grid to snap to, and draggable images (picture boxes for now) to serve as miniatures and objects and whatnot. LCDs don't usually have very high resolution, so I want the mat to be the entire window and do everything through context menus and tooltips.

I'm relearning VB after a two year hiatus just for this, and I wasn't very good at it before. I'm almost certainly going to have lots of dumb questions about this stupid thing, so I'll try to keep them all here.



I'm at a point where I have an array of picture boxes and I'm trying to redim the array to add another element, new it and everything, and add it to the form. The last step is what's causing the rukus, specifically;

Code:
Me.Controls.Add(ArrayOfMinis(i))
With that line in place, the program stops responding when I add a mini, and the debugger tells me, "An unhandled exception of type 'System.ArgumentException' occurred in system.windows.forms.dll - Additional information: Invalid parameter used." With the line commented out, the crash doesn't occur but neither does adding a mini actually do anything except create an invisible picture box.

I'm using VB .NET 2003. Attached is the project thus far. Any help is of course greatly appreciated.
Attached Files
File Type: zip battlemate10-24.zip (114.1 KB, 2 views)
__________________
Rip off every sig.
You know what you do.
Steal sig.

Last edited by FacelessSchmuck; 10-24-2008 at 10:25 PM.
Reply With Quote
  #2  
Old 10-26-2008, 09:40 AM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

Alright, so I got around that problem. Apparently creating an array of minis is completely unnecessary; Dim-ing a new control within a Sub and adding it works exactly the way I needed it to without it having to be an element of an array.

Next problem: You know the little "Bring to Front" and "Send to Back" buttons on the form designer? Is there any way to do that at runtime, altering the layering of new controls?
__________________
Rip off every sig.
You know what you do.
Steal sig.
Reply With Quote
  #3  
Old 10-26-2008, 12:27 PM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Controls have a BringToFront and SendToBack method that does the same thing.

I swear that there's a way that you can manually set the z index, but I can't seem to find it right now.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #4  
Old 10-28-2008, 08:24 AM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

Referring to controls created dynamically is a little tricky. Having to get and refer to control indices is something I've never had to do before. But the important thing is that every aspect of this thing I tackle teaches me something.

I think my approach is wrong, or at least suboptimal. For example when I create movable picture boxes and make them transparent and use them to display GIFs or PNGs with transparent backgrounds, the program interprets "transparent" to mean "show what's painted directly onto the form."

http://i19.photobucket.com/albums/b1...emate10-28.png

The screenshot contains three dynamically created miniatures (a draggable version of the PictureBox class). The grid is drawn onto the form during MyBase.Paint. The stone tiles are a JPEG set to BackgroundImage, the ball is a GIF set to Image, and the beholder is a PNG set to Image.

I'm thinking I need DirectX to achieve everything I want in this app. The learning continues.
__________________
Rip off every sig.
You know what you do.
Steal sig.
Reply With Quote
  #5  
Old 10-28-2008, 10:41 AM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Transparency in Windows Forms is broken. It's not a .NET thing: transparency in straight GDI is broken as well. When most of Windows' graphical framework was designed, computers were lucky to have more than 8MB of RAM and a video card was something that only CAD designers had; because of this GDI was optimized for displaying simple things.

Basically what you are seeing is an issue related to the fact that transparent controls cannot overlap in GDI. Now that I'm done lying to you, I can tell you that starting in Windows 2000, transparent controls can overlap, but only if they have certain class bits set. Here's a thread where I managed to figure out how to make this happen. You have to set two class styles that tells Windows to take what's underneath the control into account.

You might want to consider avoiding the use of controls entirely and just draw the pieces manually depending on how many pieces you plan on displaying at once. Having a lot of controls on a form can slow it down in a hurry. It's possible to make lightweight pseudo-controls that don't put as big of a drain on resources.

I cannot make a VS 2003 example project at this time; I had to uninstall it because I needed the disk space. I can't guarantee that my demonstration code will work as-is becuase I can't check it in VS 2003. I strongly recommend you consider downloading VB .NET 2008 Express Edition. It's free and there's no limitations on what you can do with it. The main reason for this is becuase .NET 2.0 (shipped with VS 2005) has a ton of really important improvements upon .NET 1.1 (shipped with VS 2003.) One of these improvements is generics, which allows a strongly-typed collection like List(Of T) rather than relying upon using Object for storing generic items; when storing value types such as integers using generics can result in a 15x-17x performance improvement due to the lack of boxing/unboxing.

That said, let's take a look at how you might implement virtual battle mat pieces without using controls; I will do my best to write .NET 1.1 compatible code.

First, you need a class to represent each piece. There might be more information you want to store, but for this example all we need is a location, a size, a name, and the image to display for the piece:
Code:
Public Class GamePiece

    Private _image As Image
    Private _location As Point
    Private _name As String
    Private _size As Size

    Public Property Image() As Image
        Get
            Return _image
        End Get
        Set(ByVal value As Image)
            _image = value
        End Set
    End Property

    Public Property Name() As String
       '...
    End Property
   
    Public Property Location() As Point
        ' ...
    End Property

    Public Property Size() As Size
        ' ...
    End Property

End Class
There's a few more convenience properties (like Width and Height) that I'd normally add that have been omitted for brevity. Now, let's talk about designing the battle mat. I've only got 6,000 characters left, so I'll be brief on features. We need the battle mat to do the following:
  • Display a grid background
  • Display game pieces
  • Determine if a mouse click is on a game piece or the mat
  • Get the game piece underneath a specific coordinate

I've attached a file, BattleMatControl.vb, that implements this functionality. I'm going to explain the high points, and a few things I added that may seem complicated but will help you adjust for the single unavoidable constant in software development: change. I'm going to plug .NET 2.0 along the way.

First, notice the control stores game pieces in an ArrayList, and you can add pieces via the AddPiece method. This would be a List(Of GamePiece) in .NET 2.0, and we'll see in a bit why this would be a little better.

Let's move on to drawing, as this is my favorite part. I handle the OnPaint event, and split the drawing into three different methods:

DrawBackground is responsible for drawing the background of the mat. You might have some image to represent terrain that you want to use, but for this example I'm just painting it olive drab. DrawGrid is responsible for drawing the grid. I calculate the size of each cell, then draw lines spaced equally apart to create the grid. Notice that I have to call Dispose on the pen to make sure I release its resources; in .NET 2.0 I could use a Using statement to make this automatic (I actually forgot to do it until I started typing this post!) DrawPieces draws each game piece and is worth a little more discussion. First, notice that I have to use DirectCast to convert the Object that comes out of the ArrayList into a GamePiece. If we were using a List(Of GamePiece) in .NET 2.0 this would be unnecessary. The rest of the method just draws a nearly-transparent background to represent the game piece, then draws the piece image on top of it. The background rectangle isn't needed, but it helps when a piece is mostly transparent.

Well, we can display a grid background and display game pieces, but now we need to know if a mouse click is on a piece and if so return that piece. This code is in the regions "Hit Testing" and "Piece Clicked Event". First, let's look at the event.

I want other classes to know when a piece is clicked on this control, so I decided to create a PieceClicked event. I want the event to indicate which piece was clicked, so I needed a PieceClickedEventArgs to contain this information; EventArgs-derived classes are always simple read-only things:
Code:
Public Class PieceClickedEventArgs
    Private _piece As GamePiece

    Public Sub New(ByVal piece As GamePiece)
        _piece = piece
    End Sub

    Public ReadOnly Property Piece() As GamePiece
        Get
            Return _piece
        End Get
    End Property
End Class
Finally, since .NET 1.1 doesn't have generics, I had to declare a PieceClickedEventHandler delegate to represent the event handler. This has to be public if you want other classes to handle the event. In .NET 2.0, we could have used EventHandler(Of PieceClickedEventArgs) instead. OnPieceClicked follows the standard pattern for raising events. If you look at the "Event Handlers" section, you'll find that OnMouseUp calls OnPieceClicked when the mouse button is released over a piece.

Now let's look at the actual hit-testing code. GetPieceAtLocation does the real work. It loops over every GamePiece in the array list (remember, the cast is unnecessary in .NET 2.0!) and creates a Rectangle that represents the piece. If the rectangle contains the point, this piece is considered the right one and is returned. (Note that if the GamePiece class had a Bounds property we wouldn't have to create a new rectangle each time.) If no game piece contains the location, then Nothing is returned. HitTest is a private function for more advanced scenarios; it uses GetPieceAtLocation to determine if a piece exists at the location and returns a HitTestResult value to indicate what was clicked. This may seem redundant, but it's there to handle change. What if you decide to add items to the battle mat that aren't game pieces? You can add a new item to HitTestResult and alter HitTest to check for these items as well.

The only thing left worth discussing is the OnSizeChanged handler; if the board width or height isn't a multiple of the size of the cells, you end up with a row or column that looks too small. I handle this by forcing the size to be a multiple of the row or column. This makes resizing pretty jerky. I usually handle this in my personal controls by making the grid area display centered in some kind of border region; this lets you resize the control to any size you want but maintains the size of the grid region.

I tested this with two transparent GIF images on a form with code that looked like the below code: it creates two pieces that overlap. I added the PieceClicked handler to ensure the event worked.
Code:
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim image1 As New Bitmap("piece1.gif")
        Dim image2 As New Bitmap("piece2.gif")

        Dim piece As New GamePiece()
        piece.Location = New Point(10, 10)
        piece.Size = New Size(48, 48)
        piece.Image = image1
        piece.Name = "Piece 1"
        BattleMatControl1.AddPiece(piece)

        piece = New GamePiece()
        piece.Location = New Point(34, 34)
        piece.Size = New Size(48, 48)
        piece.Image = image2
        piece.Name = "Piece 2"
        BattleMatControl1.AddPiece(piece)
    End Sub


    Private Sub BattleMatControl1_PieceClicked(ByVal sender As Object, ByVal e As PieceClickedEventArgs) Handles BattleMatControl1.PieceClicked
        MessageBox.Show(e.Piece.Name)
    End Sub
Ugh. I'm at the 10,000 character boundary. Please wait for the next post so I can finish up.
Attached Files
File Type: vb BattleMatControl.vb (4.7 KB, 1 views)
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #6  
Old 10-28-2008, 10:42 AM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

The main problem with this control as-is is what happens when two pieces overlap; currently the control that was added first is returned. There are several ways to handle this, but they would have overly complicated the example. Another problem this control doesn't address is you may not want pieces to be rectangular; you may want only the non-transparent regions of the image to register a click. In this case, you're better off making GamePiece inherit from Control, making it transparent like the post I linked indicated, and letting it paint its image. Otherwise you're going to have to define the "solid" and "not solid" regions for each image. Ouch.

Finally, I'd like to add that in .NET 3.5 you can use the Windows Presentation Foundation instead of Windows Forms to make applications. WPF was designed for advanced graphical tasks, and transparency works just fine in WPF; none of these workarounds are necessary. However, this is very different than Windows Forms so it might not be an easy route.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #7  
Old 10-29-2008, 01:39 AM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

Wow, that was really thorough. Thanks.

I got VB.NET 2008 Express (I actually got it a few days ago, but now I'm forcing myself to use it). I got a working program with what you gave me. New snag, I can't interact with the pieces. EDIT2: I think what it comes down to is a lack of understanding about EventArgs, RaiseEvent, whether the code should be myBattlemat_MouseDown or OnMouseDown within the battlemat class. Something.

Attached is my [illegitimate child]ization of your code. I've been staring at this for about five hours and Me.CanThink <> Thinking.Straight

End Post


EDIT: The most notable change I should mention which might not be readily apparent, I changed the coordinate system a bit. You can now pan around a theoretically infinite Battlemat by dragging with the middle button. Piece.Location is an absolute value based on this infinite plane, and The Battlemat has a GridOffset property (a point) which is used to render pieces to a point relative to the actual control.

The next big feature that I haven't really addressed: I want to be able to draw terrain onto the battlemat with an MSPaint-like interface, as well as save and load said terrain. I was thinking of some method that dices the battlemat into sections which either contain drawing (and are saved as PNGs) or not. In theory, this would also allow me to refrain from painting sections of terrain that aren't actually on screen.

Your help has been amazing. Thank you very much.
Attached Files
File Type: zip battlemate4.zip (36.4 KB, 1 views)
__________________
Rip off every sig.
You know what you do.
Steal sig.

Last edited by FacelessSchmuck; 10-29-2008 at 09:16 AM.
Reply With Quote
  #8  
Old 10-29-2008, 10:13 AM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

Bumping with a new version. '08 Express. Includes a die roller, a status bar, a full screen mode, and panning (middle drag), but still can't drag pieces.

http://i19.photobucket.com/albums/b1...emate10-29.png

Spore creatures work really, really well as pieces.
Attached Files
File Type: zip battlemate4-1.zip (42.2 KB, 0 views)
__________________
Rip off every sig.
You know what you do.
Steal sig.

Last edited by FacelessSchmuck; 10-29-2008 at 10:19 AM.
Reply With Quote
  #9  
Old 10-29-2008, 11:09 AM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Use a debugger and watch your code.

When I add a creature, then click on it, HitTest is called, which calls GetPieceAtLocation to check if something exists at the location. Something gets returned, and it's a creature, so HitTest returns Creature. Now, here's the code that decides whether to raise the event:
Code:
        If HitTest(e.Location) = HitTestResult.Piece Then
            OnPieceClicked(New PieceClickedEventArgs(GetPieceAtLocation(e.Location)))
        End If
Whoops! HitTest is returning Creature, which isn't Piece so the event isn't being raised.

To be really clean, it might be worth implementing a CreatureClicked event so that you can differentiate creature clicks or piece clicks. Or, it might just be best to have all board items either inherit from the same base class, implement a common interface, or be the same class (currently they are the same class). Then, create an ItemClicked event that uses its event args to indicate the type of item that was clicked and also contains the item that was clicked. If there's going to be multiple types of items, this might be the best route.

Another thing I noticed is the panning functionality is implemented on the form, even though it doesn't depend on anything special about the form. If the battle mat should have panning functionality, this functionality should be in the battlemat, not the form that implements it. This is good object-oriented design.

Anyway, I did some dumb things in the hit testing and felt like fixing them up a little bit. I wasn't caching items and was calling some methods more times than needed. Here's code changes that are a little smarter and also allow interaction with creatures.

First, let's get the nasty .NET 1.1 stuff out of the code.
Step 1: make _pieces use a generic list type:
Private _pieces As New List(Of Piece)()

Step 2: everywhere that had a cast can now avoid the cast:
DrawPieces:
Dim myPiece As Piece = _pieces(i)

GetPieceAtLocation
Dim myPiece As Piece = _pieces(i)

Now, here's the altered functions:
HitTest
Code:
    ' Returns the type of item at the indicated location.
    Private Function HitTest(ByVal location As Point) As HitTestResult
        Dim item As Piece = GetPieceAtLocation(location)

        If item IsNot Nothing Then
            If item.Type = "Creature" Then
                Return HitTestResult.Creature
            Else
                Return HitTestResult.Piece
            End If
        End If
        Return HitTestResult.Table
    End Function
The big change here is it only calls GetPieceAtLocation once now. It was calling it twice before which made debugging a pain and would slow things down as more pieces are added.

OnMouseDown
Code:
    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
        MyBase.OnMouseDown(e)

        If HitTest(e.Location) <> HitTestResult.Table Then
            OnPieceClicked(New PieceClickedEventArgs(GetPieceAtLocation(e.Location)))
        End If
    End Sub
Here, if the hit test result is not the mat, we'll raise the piece clicked event. There's a subtle reason to perhaps avoid using HitTest here, but it makes the logic more clear.

Now, with at least the last two changes, the event is being raised on my machine. I can't find any handlers for this event, so I'm not sure what else could be going wrong.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #10  
Old 10-31-2008, 07:52 AM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

New version is fully useable. You can freely pan around a practically infinite battlemat, there are three classes of pieces (tiles, objects, and creature) that can be dragged, locked and freely changed at runtime, a full-screen mode, and renders smoothly thanks to another post of yours about the DoubleBuffered property.



New questions:

1) I did this wrong. I took what you handed me and filled it with spaghetti. It technically works but I know there's a cleaner, more correct way to handle this through click events. Can you give me some general rules for how to effectively create and use each event? I plan to redo everything from the ground up when I get a solid understanding of how each aspect works.

2) How do I create a file format? I want to be able to save the list of pieces to a file to reload it later. I could just write the properties to a text file but that doesn't help me if the path of the image is no longer valid. Is there a way I can create a WritePiecesToFile sub that covers all the data in each Piece including the image (preferably in the original format rather than uncompressed bitmap)?

3) When I get around to enabling freehand painting under the grid surface, what would be the best way to save that data? My original thought was to cut the drawn area up into PNG files of a given size and put them in a new folder with an XML file listing where each section should go. This is preliminary because I don't even know how to do the drawing yet, much less saving any of it.

And of course any other suggestions, including suggestions for further reading (EDIT2: like this) so you don't have to keep writing novellas for me, are greatly appreciated.

EDIT: Speaking of redoing things from the ground up, maybe I should make a BattlematViewport control that just handles the rendering of an associated Battlemat class, the latter containing the list of pieces. That would allow me to create multiple views of the same game by creating multiple BattlematViewports which render the same Battlemat at different offsets. Hmm...
Attached Files
File Type: zip battlemate4-5.zip (46.7 KB, 3 views)
__________________
Rip off every sig.
You know what you do.
Steal sig.

Last edited by FacelessSchmuck; 10-31-2008 at 09:22 AM.
Reply With Quote
  #11  
Old 10-31-2008, 01:38 PM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Overall, the application is fairly impressive and you're not giving yourself enough credit. There are some practices that I think you could make your life easier by avoiding, but all in all you have decent organization and you aren't really doing anything that makes me want to tell you to delete it all and try again.

Let's cover your questions, then I'll make a few suggestions for the changes I think will provide the most value for the effort.

1) Actually you did a pretty good job following my pattern with events. The advanced event tutorial you found is a very good thing to follow when developing custom controls, but it's one of those "best practices vs. ease of development" things.

I could almost swear I wrote and submitted a basic events tutorial, but I can't find it anywhere on the forums, and I didn't link to it from my FAQ page, so I'm going to assume I never posted it. I'll try and dig it up when I get home. Here is the basic pattern I follow when creating an event; it's based on how the events in Microsoft's classes are created (though they do use the advanced event method) and if you follow this your classes will be good .NET citizens.
  1. Decide if your event needs to send information about the event. If it does, you need an EventArgs for your event; read the sublist. If not, use EventArgs and skip to step 2. I will assume a custom event args in examples, but you can replace MyEventArgs with EventArgs as needed.
    1. Make a class that has read-only properties for every piece of data the event needs to send.
    2. It is generally accepted that this class is named a specific way: EventNameEventArgs. So, for PieceClicked, you will name the class PieceClickedEventArgs.
    3. Make a constructor that sets each of these properties.
    4. Make sure the class inherits EventArgs.
  2. In .NET 2.0 and greater, you don't need a delegate because of the generic delegate EventHandler(Of T). So, here's an event with a custom event args and the default one:
    Code:
    Public Event SomethingHappened As EventHandler(Of SomethingHappenedEventArgs)
    Public Event NormalEvent As EventHandler
    Basically the way this meshes with the way .NET 1.1 worked is that EventHandler is a delegate that looks like this:
    Public Delegate Sub EventHandler(ByVal sender As Object, ByVal e As EventArgs)

    EventHandler(Of T) uses a new language feature called Generics; it looks like this:
    Public Delegate Sub EventHandler(Of T As EventArgs)(ByVal sender As Object, ByVal e As T)
    Without explaining generics, this basically says that T can be any type, so long as it is EventArgs or at least derives from it. Whew, small step with a big explanation.
  3. With the event declared, you need a way to raise it. Create a non-public (Protected or Private usually) method named OnEventName to raise the event:
    Protected OnSomethingHappened(ByVal e As SomethingHappenedEventArgs)
    RaiseEvent SomethingHappened(Me, e)
    End Sub
  4. When you want to raise the event, call the appropriate OnEvent method.
There's a ton of "why" questions here. I want to cover them in a tutorial later, but feel free to ask them.

2) Basically, decide what you want to save, then figure out how you want to save it in a file, then do it. You can include the images as binary data, but this will pretty much require you to use a binary file format. It's probably acceptable to just save the path to the image files. I have some ideas for some fairly efficient ways to store the images with the file, but I'll only describe them if it's really unacceptable to store the path.

3) I like your idea. This is similar to how Google Maps and Microsoft's Deep Zoom Silverlight application work. If it's good enough for them, odds are the only better methods haven't been discovered yet.

Now I have 6,000 characters left to highlight the things I saw in a quick walkthrough of your code that kind of bothered me.

Here's two key design goals that relate to each suggestion:

The first thing that kind of bugs me is code like this:
Code:
propForm.nameTextBox.Text = PieceClicked.Name
propForm.previewPicBox.Image = PieceClicked.Image
propForm.pathTextBox.Text = PieceClicked.ImagePath
My suggestion here is to write properties for these rather than directly expose the control. For example:
Code:
Public Property Name As String
    Set
        nameTextBox.Text = value
    End Set
    Get
        Return nameTextBox.Text
    End Get
End Sub
This is functionally equivalent and slightly more work, but I feel like it's a better practice (notice the ColorDialog behaves this way.) The benefit here is you are exposing an interface rather than an implementation. When exposing controls, the calling class has to know that it's working with a control. When exposing properties, you are letting the calling class work with data and encapsulating the details of the control that displays the data. To get an idea of why this is beneficial, consider the changes you would have to make in both cases if you wanted to switch to a third-party control that doesn't have a Text property. A design goal this indicates is, "Program to an interface, not an implementation."

Next, let's look at some code from the control's MouseDown handler:
Code:
                If HitTest(e.Location) = HitTestResult.Creature Then
                    RaiseEvent CreatureRightClicked(Me, New PieceClickedEventArgs(GetPieceAtLocation( _
                      e.Location)))
                ElseIf HitTest(e.Location) = HitTestResult.Other Then
                    RaiseEvent PieceRightClicked(Me, New PieceClickedEventArgs(GetPieceAtLocation( _
                      e.Location)))
There's no need to call HitTest and GetPieceAtLocation every time. These methods might get slow as more pieces are placed; you should call them sparingly. You can get around this by caching the results of the method calls:
Code:
Dim result As HitTestResult = HitText(e.Location)
Dim item As Piece = GetPieceAtLocation(e.Location)
Dim args As New PieceClickedEventArgs(item)

Select Case result
    Case HitTestResult.Creature
        RaiseEvent CreatureRightClicked(Me, args)
    Case HitTestResult.Other
        RaiseEvent PieceRightClicked(Me, args)
        ...
A good rule of thumb when writing code like this is to count how many times you call a method that calculates something. First, decide if each call is necessary; sometimes a method might return different results each time. If the result should be constant (like HitTest), then consider caching the result instead of calling the method multiple times. The rule of thumb I keep in mind is, "If you do something twice, start thinking about how to reduce the duplication. If you do something three times, you must reduce the duplication or justify it."

On a side note, I'm really starting to question if HitTest needs to exist at all; if each Piece had a Type property, then GetPieceAtLocation does all the needed work. It's an interesting alternative to consider. Just because I wrote it doesn't mean I'm right

There's a few other things here and there, but most of it is simply things that you learn by experience. This weekend I think I'm going to catalog the books I've found most useful, but I can give you the following advice about learning how to design better code:

You should probably balk on considering any books that specialize on VB .NET. It looks to me that you have a good grasp of the language itself; buying a VB .NET book at this point would be like a carpenter reading the instructions for a hammer. Instead, try to find books that approach design in a more general sense. You don't need to know how to swing a hammer anymore; you need advice on when to use a hammer and when a nailgun is better.

To this end, here's a few books I recommend:
  1. Jeffery Richter's CLR via C# -- I am not aware of a more in-depth discussion of .NET's technical features. It may focus on C#, but there's maybe 30-50 pages in the 650 page book that don't apply to VB .NET.
  2. Eric Freeman, Elisabeth Freeman et al.'s Head First Design Patterns -- So far this is probably going to be the most influential book I've read. Teaches Design Patterns (strategies for solving common problems) in a language-agnostic way (though examples are in Java). I haven't finished it yet.
  3. The Gang of Four's Design Patterns: Elements of Reusable... -- The "grownup" version of the last one. Much more in-depth, much more useful, much more dry and boring. I don't own it yet, but I've never met someone who didn't recommend it.
  4. Steve McConnell's Code Complete -- I've only read the first two chapters. If the book continues its tome, it will replace Head First Design Patterns as most influential. McConnell is teaching how to write code, not how to use a language. This is on every list of recommended books I've ever read.
  5. Andrew Hunt and David Thomas' The Pragmatic Programmer -- Short and sweet; see if you can't snag one at a discount becuase it's retail is kind of high. It hammered some good practices into me, but isn't really related to software design as much as the entire software development process.

On a final note, refuse to stop learning: that's the most important thing you can do.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #12  
Old 10-31-2008, 03:06 PM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Also, thanks to Colin_L for pointing out that this thread is the basic event tutorial I was thinking about. I got confused by the "custom events" title because that's related to the advanced events in C#.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #13  
Old 10-31-2008, 10:23 PM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

Quote:
Originally Posted by AtmaWeapon View Post
Overall, the application is fairly impressive and you're not giving yourself enough credit.
A week ago, this was just an idea. So thanks for everything.


Quote:
Originally Posted by AtmaWeapon View Post
My suggestion here is to write properties for these rather than directly expose the control.
Actually it's funny you should mention that. Before I read your post, I actually went into the form and overloaded New. New(PieceType) just uses defaults for that piece. New(Piece) pulls all the values out from that piece. The main Form is a lot cleaner now because of it.


Quote:
Originally Posted by AtmaWeapon View Post
I have some ideas for some fairly efficient ways to store the images with the file, but I'll only describe them if it's really unacceptable to store the path.
It kind of is. Suppose you save a battlemat and then send that file to someone else. Now all the images paths are invalid.

BinaryWriter doesn't seem to have a WriteImage method though, so... what next, teach'?
__________________
Rip off every sig.
You know what you do.
Steal sig.

Last edited by FacelessSchmuck; 10-31-2008 at 10:38 PM.
Reply With Quote
  #14  
Old 11-01-2008, 11:23 AM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Quote:
Originally Posted by FacelessSchmuck View Post
BinaryWriter doesn't seem to have a WriteImage method though, so... what next, teach'?
It doesn't, but that doesn't mean you can't glue some other methods together to make one.

MemoryStream is an in-memory I/O class; you can write to it, then read the data back out as an array of bytes.

Image has a Save overload that writes to a stream.

BinaryWriter has methods that can write bytes to its stream.

Putting the three together, a WriteImage might look like this:
Code:
    Private Sub WriteImage(ByRef output As BinaryWriter, ByVal img As Image)
        Dim imgBytes() As Byte

        ' Save the image to the memory stream, then read the bytes of the memory stream
        Using mem As New MemoryStream()
            img.Save(mem, Imaging.ImageFormat.Png)
            ReDim imgBytes(mem.Length)
            mem.Seek(0, SeekOrigin.Begin)
            mem.Read(imgBytes, 0, mem.Length)
        End Using

        output.Write(imgBytes, 0, imgBytes.Length)
    End Sub
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #15  
Old 11-01-2008, 11:28 AM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

As an advanced bonus, you could use the Extension Methods feature in VB .NET 2008 to add the method to BinaryWriter itself. To make an extension method, make a module somewhere in your project; I tend to call this module Extensions. Now, define methods in it marked with the Extension attribute. The first parameter to the method indicates the type of the object it will be defined on.

So, if we redo WriteImage:
Code:
    <System.Runtime.CompilerServices.Extension()> _
    Public Sub WriteImage(ByRef output As BinaryWriter, ByVal img As Image)
        ' Same as before
    End Sub
Now we can use it as if it were a method of BinaryWriter:
Code:
        Using bw As New BinaryWriter(New FileStream("test.bin", FileMode.Create))
            bw.WriteImage(img)
        End Using
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #16  
Old 11-02-2008, 07:14 PM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

I'd just like to take a moment to let you all know that the way you set up Context Menus in VB.NET 2008 is the most unstable buggy piece of garbage I've ever seen in a dev environment. The environment has crashed about a dozen times in the last two days as a result of me trying to do seemingly normal things like inserting a separator.

I'm having some problems with zoom (I want to zoom in/out on the mouse location on wheelup/wheeldown), but I wanna get Undo and Redo working before I post a new version.
__________________
Rip off every sig.
You know what you do.
Steal sig.
Reply With Quote
  #17  
Old 11-02-2008, 11:47 PM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Quote:
Originally Posted by FacelessSchmuck View Post
I'd just like to take a moment to let you all know that the way you set up Context Menus in VB.NET 2008 is the most unstable buggy piece of garbage I've ever seen in a dev environment. The environment has crashed about a dozen times in the last two days as a result of me trying to do seemingly normal things like inserting a separator.
Then you are doing something wrong. I do have choice words about how context menus are done, but I have never experienced instability as the result of using context menus. Perhaps you should discuss what you're doing and we can figure out where things are going wrong.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #18  
Old 11-03-2008, 11:04 AM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

Quote:
Originally Posted by AtmaWeapon View Post
Then you are doing something wrong. I do have choice words about how context menus are done, but I have never experienced instability as the result of using context menus. Perhaps you should discuss what you're doing and we can figure out where things are going wrong.
It'd have to happen again for me to give you the literal text of the error. I didn't want the checkmargin to show on any of the child menus, but there's no property for it in that stupid Edit Items window they give you so I just created separate ContextMenuStrip objects for each child menu I wanted and just set each one in the ContextMenu property of the appropriate items on the main menu.

I set it up in ascending order, meaning I build the lowest level child menus first, then build higher level menus and included the lower ones, and I did generalMenu last. Once I had set it up, changing anything would crash the environment. I'd click on the icon, then the little Tasks button, then Edit Items, and try to insert a separator on that window, and it would crash. I'd try changing the text of a menu item in the designer window, it would crash.

Today, no exaggeration, I go into the form designer, I click on the icon for one of the child menus to bring it up, I click on "Add text" at the bottom of it with the intention of adding a new item, and the environment crashed. That's literally all I did. And then after I got the environment back up and recovered my project, it would not let me into the form designer until I had removed all of my menus from the code (meaning the mybase.new form designer code). I commented it out rather than deleting it, but it's useless to me either way now.

I actually got an Error Reporting window I've never seen before out of it. "Some unexpected errors have happened to software you recently used. You chose to send these error reports to Microsoft at a later time," because the only options it gave me were Send Later, Debug, and Cancel.

So now I have to set up all my menus from scratch again. But at least now I know to back everything up before trying.



I changed the coordinate system again. All distances stored within Battlemat—Miniature.Size, Miniature.Location, and _offset—now represent squares on the virtual grid. When it gets to drawing, I just multiply these values by GridSize to translate them to pixels on screen. Zooming then looks like this:

Code:
    Public Function ZoomIn(ByVal MouseLocation As Point) As Boolean
        'Scales the grid up from a given point.
        If _grid < _UpperLimit Then
            Dim CoordsBefore As New DecimalPoint(MouseLocation.X / GridSize, MouseLocation.Y / GridSize)
            _grid /= _factor
            Dim CoordsAfter As New DecimalPoint(MouseLocation.X / GridSize, MouseLocation.Y / GridSize)
            _offset.X += (CoordsAfter.X - CoordsBefore.X)
            _offset.Y += (CoordsAfter.Y - CoordsBefore.Y)
            Return True
        End If
        Return False
    End Function
I made it a function so that I would only have to invalidate if it worked. In case it's not obvious, DecimalPoint is a structure I wrote to duplicate the functionality of a Point with decimal instead of integral values.



I started having some success with undo, but I'm changing the way I do that too. I want Undo and Redo to be handled entirely by the form so I created a MiniChanged event that reports the mini before the change and the mini after.

I need sleep.

Edit1: Is there any way I can set an OpenFileDialog to always View Thumbnails instead of defaulting to List? Please don't tell me I have to build a custom file dialog.
__________________
Rip off every sig.
You know what you do.
Steal sig.

Last edited by FacelessSchmuck; 11-03-2008 at 11:43 AM.
Reply With Quote
  #19  
Old 11-03-2008, 12:29 PM
AtmaWeapon's Avatar
AtmaWeaponVirtual Battlemat AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Code:
Is there any way I can set an OpenFileDialog to always View Thumbnails instead of defaulting to List? Please don't tell me I have to build a custom file dialog.
I'm afraid the answer is you have to at the very least derive a class from OpenFileDialog; it looks like it takes some mucking about with the API. This project seems to get the job done, but I can't seem to find documentation for the trick it's using so I consider it dubious. I suppose the reason for this oversight in the .NET dialog is it has to support all dialogs from Win2k to Vista, and they don't necessarily all provide the same capabilities outside of the core functionality. In fact, Vista has a different dialog for open and save, so I'm not certain the trick will work there.

With respect to the context menus, I have a suspicion but can't prove it. First, if you don't want to show the check margin or image margin, set the menu's ShowCheckMargin and ShowImageMargin properties to false. That's not really the problem though. It sounds like you were mixing manual creation of the menus and using the Windows Forms designer. In other words, it sounds like you were dragging a context menu onto the form, but then modifying the code to manually create the menu. Generally, it's best to either use only the Windows Forms designer or manual creation. If you mix the two approaches I can see how you might end up in a state that utterly confuses VS. I don't wholeheartedly back this opinion, though; I'd rather see the code or a list of steps and try to reproduce it myself because something that crashes VS is generally a serious error and there might be something wrong with your installation.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #20  
Old 11-03-2008, 09:03 PM
FacelessSchmuck FacelessSchmuck is offline
Freshman
 
Join Date: Oct 2004
Posts: 35
Default

Quote:
Originally Posted by AtmaWeapon View Post
First, if you don't want to show the check margin or image margin, set the menu's ShowCheckMargin and ShowImageMargin properties to false.
Okay, let's look at a menu I have partially set up.

http://i19.photobucket.com/albums/b1...menussuck1.png

I set ShowCheckMargin to false on the parent menu but the child menu still has one. We don't want that.

Open the little tasks window and click on Edit Items...

http://i19.photobucket.com/albums/b1...menussuck2.png

...brings you to here.

http://i19.photobucket.com/albums/b1...menussuck5.png

Now when you select GeneralMenu, the available properties include ShowCheckMargin and ShowImageMargin, which are both set to False, but if you try to look at the File menu children...

http://i19.photobucket.com/albums/b1...menussuck3.png

It's gone. ShowCheckMargin doesn't exist for menu items, only for menus.

http://i19.photobucket.com/albums/b1...menussuck4.png

It's not an option on the children either because they are also menu items.

The only way I could come up with to create child menus without a check margin was to create separate menu strips for each child menu and then setting it to the DropDown property of the appropriate menu item in the parent menu.

Quote:
Originally Posted by AtmaWeapon View Post
It sounds like you were mixing manual creation of the menus and using the Windows Forms designer.
The only time I messed with the code was when the Form Designer (meaning the visualization of the form) refused to load. It instead gave me a list of errors similar to the error pane with links to the location of the error in the code, and all these errors happened to be in the Form Designer code window. All of the definitions and property settings of my menus were underlined.

I'm about to try setting them up again and I'm going to try to make sure I don't need to change anything, but if anything happens I'll be sure to document the hell out of it.
__________________
Rip off every sig.
You know what you do.
Steal sig.
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off

Forum Jump

Advertisement:





Free Publications
The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET.
subscribe
Programmers Heaven C# School Book -Free 338 Page eBook
The Programmers Heaven C# School book covers the .NET framework and the C# language.
subscribe
Build Your Own ASP.NET 3.5 Web Site Using C# & VB, 3rd Edition - Free 219 Page Preview!
This comprehensive step-by-step guide will help get your database-driven ASP.NET web site up and running in no time..
subscribe
Virtual Battlemat
Virtual Battlemat
Virtual Battlemat Virtual Battlemat
Virtual Battlemat
Virtual Battlemat
Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat Virtual Battlemat
Virtual Battlemat
Virtual Battlemat
 
Virtual Battlemat
Virtual Battlemat
 
-->