12,501,858 members (50,226 online)
alternative version

#### Stats

14.4K views
32 bookmarked
Posted

, 15 Feb 2010 CPOL
 Rate this:
A new approach to 2D - geometric computation replaces Point, Size, and Rectangle.

## Introduction

In my previous article SizerPanel and CaptionPanel, I had mentioned how it annoys me that `System.Drawing.Point`, `.Size`, and `.Rectangle` always force me to write the same code twice: first for the horizontal direction, and then for the vertical. And I had also mentioned about the structure `Vector` that I wrote to replace `System.Drawing.Point` as well as `System.Drawing.Size`. I also extended my geometric stuff with a `Rect` structure to replace `Rectangle`.

The basic idea of this is to make horizontal and vertical access indexed: index 0 accesses horizontal, index 1 vertical. Now I can write geometric code for only one direction, and the other will be covered by swapping the indices 0 and 1.

So my primary goal was not to create a remake of StickyWindows, but to test, proof, and improve the power of my geometric approach. I took StickyWindows as the topic because I liked the idea, and also the implementation-challenge, especially the geometric part is not that trivial (in the original, the pure geometric computing-code took about 250 lines). It's an excellent "sparring-partner" to become fit in geometric computations, and to try out my "weapons".

## What sticky/snap windows do

They make forms perform a kind of "magnetic behavior": when you resize or move a form, and it gets close enough to another form, the near edge snaps to the edge of the other. That really makes it easier to arrange multiple forms on screen in a clear manner.

## Implementation challenges

### Subclass the Form

You can't use the `Resize` or `SizeChanged` event to perform form-snapping, because that implies changing the size within the `SizeChanged` event itself. My first trials were in that direction, and they ended up in some real puzzling behavior. Nor will you get `MouseMove` events, because a `Form` is just resized, when especially the non-client-area is hit, and there is no `MouseMove` available. So you must access the required data before it appears on the .NET-managed level - in other words: you must subclass the `Form`. To achieve this, .NET provides two ways: the simpler one is to override the `WndProc` Sub in each `Form` you want to behave as a `SnapWindow`. The second way is to instantiate a `NativeWindow`, assign the target form's handle, and override the `NativeWindow`'s `WndProc()`. The `WndProc()` will handle all the window messages to the target form. And I still believe as in my previous article, that this is the more flexible way, because you don't need to modify each form's user code, but can simply register a `Form` as a `SnapWindow`, from the outside.

Now, take a look at the subclassing code:

```Protected Overrides Sub WndProc(ByRef m As Message)
Const WM_NCLBUTTONDOWN As Integer = 161
Const WM_MOVING As Integer = &H216
Const WM_SIZING As Integer = &H214
Select Case m.Msg
Case WM_NCLBUTTONDOWN
'init a VirtualForm
Dim snapRects As New List(Of Rect)
_VForm = VirtualForm.TryCreate(_Form, m.WParam.ToInt32, snapRects)
If _VForm.Null Then Exit Select
'...
'... set up snapRects
'...
Case WM_MOVING, WM_SIZING
'write the computed Rect to LParam
Marshal.StructureToPtr(_VForm.GetSnapFormRect, m.LParam, False)
End Select
MyBase.WndProc(m)
End Sub```

As it often happens: It's easy when you know how... ;) - in this case, we need to know how to handle the members of the window message:

`m.Msg` indicates the type of event to perform: `WM_NCLBUTTONDOWN` - WindowMessage about a Non-Client-area-Left-ButtonDown - indicates the beginning of a `Form` move or resize. `WM_MOVING`, and `WM_SIZING` are self-explanatory, aren't they?

When the form-bounds start to change (`WM_NCLBUTTONDOWN`), I initialize an instance of my `VirtualForm` class, passing three things to it: the `Form`, the `snapRects` (a list of `Rectangle`s which can be snapped), and the window message's `.WParam` - uh?

`m.WParam` - this is more tricky than `m.Msg`. In general, the `WParam` can mean everything. But in combination with the `WM_NCLBUTTONDOWN` message, it passes one of the following `HT` (Hit-Test) constants: `HTCAPTION`, `HTLEFT`, `HTRIGHT`, `HTTOP`, `HTTOPLEFT`, `HTTOPRIGHT`, `HTBOTTOM`, `HTBOTTOMLEFT`, `HTBOTTOMRIGHT` (and some other, which are out of interest - `TryCreate()` rejects them, if they are there). Now, the `VirtualForm` knows exactly what kind of bounds-changing is requested. (You will already suspect: `VirtualForm` is designed to compute snapping form-bounds).

Well, and the next time we meet our `VirtualForm` is when a moving or a sizing occurs - and the code-comment already mentions this: the computing result is written to the `LParam` pointer - and all of the form-bounds-change-task is done! You see:

`m.LParam` is tricky: combined with a move/size message, it is a pointer which points to the `RECT` structure which defines the form-bounds after the change. And, changing the `RECT` structure changes the bounds.

At this point, unmanaged code is directly accessed - since `LParam` is an `IntPtr`, you cannot simply assign a `Rectangle` to it. You must copy the `Rect` structure byte wise to the place in memory where the pointer points to. This does `Marshal.StructureToPtr()` for us. And we hope that the data structure the WinAPI expects at that place in memory is compatible with the structure you submitted. Believe it or not - my `Rect` structure is compatible ;).

Note that the WinAPI `RECT` structure is different to the `System.Drawing.Rectangle` structure. `Drawing.Rectangle` defines a rectangle by a location-point and a size, while the WinAPI `RECT` defines the top-left point (equals location) and the bottom-right point (not equal to `Size`). I, for instance, found it more useful to see those things the "API-way", and that's why my `Rect` structure fits better to WinAPI than `Drawing.Rectangle` does.

#### From where did I get to know about the subclassing stuff?

(To be honest, I picked it up from the internet.) A more systematic method to find the info would be to open the Help-Index (offline-MSDN), set the filter to "Platform SDK", and type `"WM_MOVING"`, `"WM_SIZING"` or `"WM_NCLBUTTONDOWN"` in the search-textbox. This brings up informative articles about these constants, and how you can work with them.

Unfortunately, the values of the constants aren't mentioned anywhere. You can download the VS2008 Platform SDK. This will install in your Program Files, and you will find a 'C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\WinUser.h' on your system, where (among other things) all constants of the user32.dll are defined.

## Vector and Rect

A Vector is a Size and a Point in one. It provides `X`, `Y` and usual features to perform simple 2D-geometric math. I made a bunch of `CType` operators to make it as flexible and compatible as possible. Its special feature is that you can access `X` and `Y` by using an `Integer` index in order to avoid writing each piece of geometric code in two variants.

`Rect` simply is two `Vector`s. As I mentioned before, it doesn't define the described rectangle as a composition of a Point and a Size, but defines the top-left Point and the bottom-right one. I call them `.Near` and `.Far`, as a homage to the `System.Text.StringAlignment` enumeration, which is the only item I know in the Framework which also works in both dimensions (you can align a text-drawing horizontally by setting `StringFormat.Alignment.Near/.Center/.Far`, and align it vertically by setting `StringFormat.LineAlignment.Near/.Center/.Far`). In following sections, I will use the terms Near or Far to distinguish between a top or left entity from a bottom or right one. And, of course, `Near` and `Far` are accessible by index too.

## VirtualForm

It only gets data once, in the constructor: `Rects` and `Vectors`. When new form-bounds are requested, it first computes the normal form-bounds, only using the globally accessible `Control.MousePosition` as additional data. In the second step, all "snap-candidate-rectangles" are compared with the computed normal-form-bounds, and the edge with the minimum distance gets snapped. This is done with the horizontal edges as well as with the vertical ones.

VirtualForm must compute the `NormalFormRect` without dependence to the real form, because the real bounds are different when another form's edge is snapped.

The `NormalFormRect` must be known to perform the "snapping off", when the distance between the `NormalFormRect` and a snapped edge exceeds the "magnetic distance".

So first, we have to understand how the `NormalFormRect` is computed, that means: comprehend how a mouse moves or resizes the form.

In fact, there is no difference between moving a form and resizing it, if you understand the process a bit more in general. You will see that it is always done by dragging edges: you can drag one edge (mouse grabs a form-edge), two edges (mouse grabs a corner), or four edges (mouse grabs the caption (=title-bar)). (To drag three edges is a theoretical option, but not provided by the infrastructure.)

Edge-dragging must be initialized by storing the information about which edges are affected, and also storing the "Grab Offset", which is the distances of the affected edges to the mouse-position. The VirtualForm constructor does this in two lines of code:

```_SizeType = HT2SizeOption(HT)
_GrabOffs = Rect.From(Control.MousePosition).Subtract(_FormRect)```

(For simplicity in code, `_GrabOffs` stores all edge-distances, although only the affected ones are needed.)

Now, it can recompute the NormalFormBounds, whenever requested, since the drag-principle is the simple: always keep the offset constant. But recomputing isn't quite that simple, because I'm not allowed to change all edges, and must distinguish between the edges according to `_SizeType`.

To understand in depth how I achieve that distinction, let me do an egression about...

Enumerations are useful to store and query multiple boolean statements. With the bit operators `Or`, `And`, and `Xor`, you can filter and modify all the 32 bool-statements of an (`Int32`-)enumeration-member with one command. One of the first things the `VirtualForm` constructer does is call `HT2SizeOption()` to map the passed `HT`-constant to an enumeration named `SizeOption`.

```Public Enum SizeOption As Integer
Top = 1
Right = 2
Bottom = 4
Left = 8
End Enum```

For brevity in code, I added the mainly used bit-combinations too so that the complete definition comes up as:

```Public Enum SizeOption As Integer
Top = 1
Right = 2
Bottom = 4
Left = 8
TopLeft = Top Or Left
TopRight = Top Or Right
BottomLeft = Bottom Or Left
BottomRight = Bottom Or Right
Caption = Top Or Left Or Bottom Or Right
End Enum```

No rocket science, is it? And it defines clearly the nine options to drag a form.

Now you can check out whether a given (multiple) bool-statement (represented by an enumeration value) has one or more bits in common with a given other one:

`(_SizeType And SizeOption.Top) = SizeOption.Top`

will match, if `_SizeType` is one of the following: `Top`, `TopLeft`, `TopRight`, `Caption`.

Another way to query is:

`(_SizeType And SizeOption.TopLeft) <> 0`

This will match, if any of the `_SizeType` bits match any of the `TopLeft` bits. In particular: `Top`, `Left`, `TopLeft`, `TopRight`, `BottomLeft`, `Caption`.

If you query against a 1-bit-statement, the code for comparison to 0 is shorter than the first variant:

`(_SizeType And SizeOption.Top) = SizeOption.Top`

has the same results as:

`(_SizeType And SizeOption.Top) = 0`

### Back to rectangle-computation

Indexable structures also imply that I can iterate them. In the case of `Rect`, I can iterate in two different ways: from near to far as well as from horizontal to vertical. I can nest in two ways and end up with a two-dimensional iteration.

```'(Dim rct As Rect)
For far = 0 To 1
For vertical = 0 To 1
Dim edge As Integer = rct(far, vertical)
Next
Next```

This loops the four edges in the following order: `Left`, `Top`, `Right`, `Bottom`. And now, I build four "enumeration-queries", which can be iterated in the same way: I build a two-dimensional `Array` of `Enum`s:

```Private Shared _SizeOptions As SizeOption(,) = New SizeOption(,) { _
{SizeOption.Left, SizeOption.Top}, _
{SizeOption.Right, SizeOption.Bottom}}```

Now I can iterate `_SizeOptions`, `_FormRect`-edges, and `_GrabOffset`s within a 2D-loop, select the affected edges, and assign values, to make the distances to the mouse position equal to the corresponding offset (principle of dragging).

```Private Sub ComputeFormRect()
Dim ptMouse = Vector.From(Control.MousePosition)
For far = 0 To 1
For vertical = 0 To 1
If 0 <> (_SizeType And _SizeOptions(far, vertical)) Then
_FormRect(far, vertical) = ptMouse(vertical) - _GrabOffs(far, vertical)
End If
Next
Next
End Sub```

### Computing the SnapRect

The whole story:

```Public Function GetSnapFormRect() As Rect
ComputeFormRect()
Dim result = _FormRect
Dim snapSize = Vector.From(SnapWindow.SnapSize)
For vertical = 0 To 1
Dim horizontal = vertical Xor 1
'get min-distance between _FormRects dragged
'edges and any edge of any _SnapRect
Dim minDist = Integer.MaxValue
For far As Integer = 0 To 1
'filter dragged edge(s)
If 0 = (_SizeType And _SizeOptions(far, vertical)) Then Continue For
For Each rct2 In _SnapRects.Where( _
Function(r2) Intersect1D(r2, inflated, horizontal))
For far2 As Integer = 0 To 1
Dim distance = _FormRect(far, vertical) - rct2(far2, vertical)
If distance.Abs() < minDist.Abs() Then minDist = distance
Next
Next
Next
If minDist.Abs() > SnapWindow.SnapSize Then Continue For
For far = 0 To 1
'filter dragged edges
If 0 <> (_SizeType And _SizeOptions(far, vertical)) Then _
result(far, vertical) -= minDist
Next
Next
If _SizeType <> SizeOption.Caption Then
'clip near-edges (WinApi cares for the far-edge-restriction)
result.Near = result.Near.Intersect(_NearClip.Far).Union(_NearClip.Near)
End If
Return result
End Function```

Now we will examine it step by step:

```ComputeFormRect()
Dim result = _FormRect
Dim snapSize = Vector.From(SnapWindow.SnapSize)

is not that complicated: compute the normal `_FormRect` (a class member) and set it as the default return value. Then, create `inflated`, as `_FormRect`, inflated by the magnetic distance.

Then, enter the two-dimensional rectangle-loop:

```For vertical = 0 To 1
Dim horizontal = vertical Xor 1
Dim minDist = Integer.MaxValue
For far As Integer = 0 To 1
'...```

Note the second line: `Dim horizontal = vertical Xor 1`. `valueToSwitch Xor 1` - the easiest way to switch an index from 0 to 1 and reverse.

Now we come to the kernel. First, skip the computation if the `_FormRect` edge in question isn't moveable at all (check against `_SizeType`):

`If (_SizeType And _SizeOptions(far, vertical)) = 0 Then Continue For`

If `_SizeType` matches with the `(far, vertical)` addressed `_SizeOption`, we loop `_SnapRect`s in order to search the minimum-distance-edge to the `(far, vertical)` addressed `_FormRect` edge:

```For Each rct2 In _SnapRects.Where(Function(r2) Intersect1D(r2, inflated, horizontal))
For far2 As Integer = 0 To 1
Dim distance = _FormRect(far, vertical) - rct2(far2, vertical)
If distance.Abs() < minDist.Abs() Then minDist = distance
Next
Next```

Again, there is a filter: `SnapRects.Where(Function(r2) Intersect1D(r2, inflated, horizontal))` only takes those `Rect`s to examine, which intersect in the other direction with the `inflated` FormRect (mentioned before). Because, it doesn't make sense to snap an edge 15 pixels to the left, when it is located about 500 pixels below ;). Note: Here, I get an one-dimensional understanding of intersection: a rectangle may intersect in the horizontal direction, but not vertical (e.g., words in a line intersect horizontal, but not vertical ). See the intersection computation code:

```Private Function Intersect1D(ByVal rct1 As Rect, _
ByVal rct2 As Rect, ByVal vertical As Integer) As Boolean
Return rct1.Far(vertical) >= rct2.Near(vertical) _
AndAlso rct2.Far(vertical) >= rct1.Near(vertical)
End Function```

OK, now we have our `minDist`, and can apply it to our return-result (if `minDist` is small enough):

```If minDist.Abs() > SnapWindow.SnapSize Then Continue For
For far = 0 To 1
'apply minDist only to dragged edges
If 0 <> (_SizeType And _SizeOptions(far, vertical)) Then _
result(far, vertical) -= minDist
Next```

followed by the (I hope so) last point of surprise:

```If _SizeType <> SizeOption.Caption Then
'clip near-edges (WinApi cares for the far-edge-restriction)
result.Near = result.Near.Intersect(_NearClip.Far).Union(_NearClip.Near)
End If```

Eh? Why does WinAPI only care for far-edge-restriction, but not restrict the near-edges? What does it mean at all: "caring for edge-restriction"? To illustrate (and for your pleasure), I created a little demo, to show what happens if you comment out that final clip:

Got it? Violating the `MinimumSize` from above is prevented by setting the form's `Height` back to the minimum amount. But it was the Top which changed! - and so the form moves, although you're actually sizing the top edge.

## Some points of interest left open

### Vector.Intersect() and Vector.Union()

Let's begin with the above mentioned clip:

`result.Near = result.Near.Intersect(_NearClip.Far).Union(_NearClip.Near)`

`Intersect()` and `Union()` are not applied to `Rect`s, but to `Vector`s! How can a `Vector` intersect with another - it has no size! And that's the point. Since a `Vector` represents a point as well as a size, it is a size. And when I intersect two `Vector`s, I take them as two `Rect`s with `.Near-Vector= (0,0)`. In another view: `Vector1.Intersect(Vector2)` returns the minimum amount of both in each direction. And, that's exactly the one (the ceiling-) part of clipping a `Vector` inside a `Rect`. The other (floor-) part of clipping is a `Union` with the `Rect`'s `.Near-Vector`, which means the maximum amount in each direction. In summary: you clip the `Vector v` within the `Rect rct` by `Intersect`ing the `union(v, rct.Near)` with `rct.Far`.

### Rects way of intersection

`System.Drawing.Rectangle.Intersect(other As Rectangle)` returns `Rectangle.Empty`, if the `Rectangle`s do not intersect. Instead of that, `Rect.Intersect()` is computed as follows:

```''' <summary>
''' a none-intersection is indicated by returning an imaginary Rect
''' </summary>
Public Function Intersect(ByVal other As Rect) As Rect
For i = 0 To 1
Intersect.Near(i) = Near(i).Max(other.Near(i))
Intersect.Far(i) = Far(i).Min(other.Far(i))
Next
End Function

''' <summary>true, if my size has a negative component</summary>
Public ReadOnly Property IsImaginary() As Boolean
Get
If Far.X - Near.X < 0 Then Return True
Return Far.Y - Near.Y < 0
End Get
End Property```

The advantage is that an imaginary `Rect` still delivers a valuable information: namely, the distance between both `Rect`s. Got it? The distance between two rectangles is understood as negative intersection.

## The idea of "homogen structure"

Structures whose fields are only of one data type, I address them as "homogen". In my opinion, we can apply some principles to them, e.g., the indexablity I already explained.

In general, you can try to transfer features from the unit-types to the whole structure.

### Inverting operator

Since `Integer` is invertable, I want `Vector` and `Rect` also to support it:

```Public Shared Operator -(ByVal v1 As Vector) As Vector
Return New Vector(-v1.X, -v1.Y)
End Operator

Public Shared Operator -(ByVal r As Rect) As Rect
Return New Rect(-r.Near, -r.Far)
End Operator```

### Data-stretching

This means several operations may be applicable, although there may be passed too few data to explicitly assign to each member.

For instance, the operation `.From()`. I take it as a public shared constructor, a bit more comfortable than the good old `New()`:

`Dim v = Vector.From(x As Integer, y As Integer)`

That is the basic one. But also unambiguous is to do this:

`Dim v = Vector.From(amount As Integer)`

It only can mean to assign `amount` to `v.X` as well as to `v.Y`.

See a data stretching equality-comparison here:

`Dim b = v.EachEquals(amount As Integer)`

And this is convenient: Suddenly, I have a sort of "inferred constants" (inferred by `Integer`). That means, `Vector.From(0)` is a perfect substitute of `Point.Empty`. And I'm free to define other "special-points", e.g., `Vector.From(Integer.MaxValue)` or `Vector.From(Integer.MinValue)`, to indicate (by convention) an invalid location without using the `Nullable(Of T)` structure.

Checking against each special-point is covered by `Vector.EachEquals()`, e.g., `Vector.EachEquals(0)` substitutes `Point.IsEmpty`, and `Vector.EachEquals(Integer.MinValue)` is a new feature with no equivalent in `Point` or `Size`, maybe at best the really ugly:

```Dim pt = New Point(Integer.MinValue, Integer.MinValue)
Dim b = pt = New Point(Integer.MinValue, Integer.MinValue)```

#### But go on stretching data

Theoretically, I could overload the `+` operator as follows:

```Public Shared Operator +(ByVal v As Vector, ByVal amount As Integer) As Vector
Return New Vector(v.X + amount, v.Y + amount)
End Operator```

to puzzle the audience with expressions like:

`Dim v = Vector.From(4, 5) + 9`

But that's too inconsiderate for even me. ;) Instead, see my `Add()` overloads, the second one does data stretching:

```Public Function Add(ByVal x As Integer, ByVal y As Integer) As Vector
Return New Vector(Me.X + x, Me.Y + y)
End Function

Public Function Add(ByVal amount As Integer) As Vector
Return New Vector(X + amount, Y + amount)
End Function

Public Function Add(ByVal v As Vector) As Vector
Return New Vector(X + v.X, Y + v.Y)
End Function```

This performs Offset, Inflate/Deflate, and is compatible to `Point` and `Size`, because of some widening `CType()` operators. (`.Subtract()` works in the same manner.)

#### Datastretching with Rect

The benefits of `.From()` and `.EachEquals()`, of course, work as well, and in the case of `.From()`, I support one more stretch option:

```Public Shared Function From(ByVal eachMember As Integer) As Rect
Return Rect.From(Vector.From(eachMember))
End Function

Public Shared Function From(ByVal eachMember As Vector) As Rect
Return New Rect(eachMember, eachMember)
End Function```

Also `.Add()` becomes more interesting:

```Public Function Add(ByVal toEach As Integer) As Rect
End Function

''' <summary>performs offset </summary>
Public Function Add(ByVal toEach As Vector) As Rect
Return New Rect(Near + toEach, Far + toEach)
End Function

''' <summary>
''' performs sophisticated offsets, eg. inflating/deflating (if v1 = -v2)
''' </summary>
Public Function Add(ByVal v1 As Vector, ByVal v2 As Vector) As Rect
Return New Rect(Near + v1, Far + v2)
End Function

''' <summary>
''' performs sophisticated offsets, supports compatiblity to Rectangle
''' </summary>
Public Function Add(ByVal r As Rect) As Rect
Return New Rect(Near + r.Near, Far + r.Far)
End Function```

(`.Subtract()` ditto.)

The above works behind the scenes, when VirtualForm gets the four-edges-offset within one line of code:

`_GrabOffs = Rect.From(Control.MousePosition).Subtract(_FormRect)`

or when I inflate `_FormRect` with `snapSize`:

`Dim inflated = _FormRect.Add(-snapSize, snapSize) `

### LINQ

```''' <summary>enables Linq-power to Vectors Elements</summary>
Public Function GetEnumerator() As IEnumerator(Of Integer) Implements _
IEnumerable(Of Integer).GetEnumerator
Return DirectCast(New Integer() {X, Y}, IEnumerable(Of Integer)) _
.GetEnumerator
End Function

Private Function GetEnumerator1() As IEnumerator Implements _
IEnumerable.GetEnumerator
Return DirectCast(New Integer() {X, Y}, IEnumerable(Of Integer)) _
.GetEnumerator
End Function

''' <summary>enables Linq-power to Rects elements</summary>
Public Function GetEnumerator() As IEnumerator(Of Vector) Implements _
IEnumerable(Of Vector).GetEnumerator
Return DirectCast(New Vector() {Near, Far}, IEnumerable(Of Vector)) _
.GetEnumerator
End Function

Private Function GetEnumerator1() As IEnumerator Implements _
IEnumerable.GetEnumerator
Return DirectCast(New Vector() {Near, Far}, IEnumerable(Of Vector)) _
.GetEnumerator
End Function```

I don't know whether that is useful. I, for instance, don't use it. But it looks interesting to me, and it was easy to implement. ;)

## Replacing public shared functions with extension functions

It's a sort of off-topic, but I'd like to mention one more principle, to make you understand all the shown code:

When it comes to extension functions, in many cases, there is no more need for `Public Shared` functions, which forces you to type full qualifiers to call them. Compare these clip-variants:

```Public Function ClipIn(ByVal pt As Point, _
ByVal rct As Rectangle) As Point
Return New Point( _
Math.Max(Math.Min(pt.X, rct.Right), rct.X), _
Math.Max(Math.Min(pt.Y, rct.Bottom), rct.Y))
End Function

Public Function ClipIn2(ByVal pt As Point, _
ByVal rct As Rectangle) As Point
Return New Point(pt.X.Max(rct.X).Min(rct.Right), _
pt.Y.Max(rct.Y).Min(rct.Bottom))
End Function```

In the second variant, `Min()/Max()` appears as an extension function, which is shorter, easier to type (help from intellisense), and which resolves the nasty nesting of functions to an easier to handle concatenation. The same concept I applied to `Math.Abs()` (maybe you have realized that already, seeing my previous code-samples).

Even tests to `Nothing`, I'd prefer as an extension:

```<Extension()> _
Public Function Null(Of T As Class)(ByVal Subj As T) As Boolean
Return Subj Is Nothing
End Function

<Extension()> _
Public Function NotNull(Of T As Class)(ByVal Subj As T) As Boolean
Return Subj IsNot Nothing
End Function```

## More ideas - the third dimension

I don't really know much about DirectX, but I wonder how DirectX developers manage not to lose their minds. Aren't they forced to type each bit of code thrice? Or, worse, `2.Pow(2)` times? I don't know the exact increase in complexity depending on the dimensions, but a square on a chess-board has 8 neighbors, while a packed cube touches 26 others!

I wonder what a "StickyCubes" application looks like? ;)

## Share

 Germany
No Biography provided

## You may also be interested in...

 First Prev Next
 Any mdi sample of this project? Member 126174297-Jul-16 16:53 Member 12617429 7-Jul-16 16:53
 I Would like a single DLL frozerlaxegon13-Aug-10 23:37 frozerlaxegon 13-Aug-10 23:37
 Great work Corneliu Tusnea15-Feb-10 15:22 Corneliu Tusnea 15-Feb-10 15:22
 Great but what about AspectRatio ? fmaeseele15-Feb-10 8:17 fmaeseele 15-Feb-10 8:17
 Re: Great but what about AspectRatio ? Mr.PoorEnglish15-Feb-10 13:01 Mr.PoorEnglish 15-Feb-10 13:01
 Excellent explanations The Manoj Kumar15-Feb-10 6:27 The Manoj Kumar 15-Feb-10 6:27
 Last Visit: 31-Dec-99 18:00     Last Update: 25-Sep-16 9:43 Refresh 1