Determine if button is inside contour - vb.net

I have a PictureBox on which a contour is drawn. Let's say this contour is in form of a circle. Now when I click a button a lot of buttons are created over the PictureBox row by row until the PictureBox is full:
For n As Integer = 1 To buttonNumberY
For m As Integer = 1 To buttonNumerX
Dim Btn As New Button
Btn.Width = elementsize
Btn.Height = elementsize
Btn.Flatstyle = Flatstyle:Flat
Btn.FlatAppearance.BorderSize = 1
Btn.FlatAppearance.BorderColor = Color.Gray
Btn.Location = new Point(elementsize*(m-1)+BORDER, maxContourHeight * scaley + BORDER - elementsize * n)
Btn.BackColor = Color.Transparent
Btn.Name = "Btn" & m & n
PictureBox1.Controls.Add(Btn)
Next
Next
As true transparency doesn't exist in WinForms I used some code so that the contour will be visible through the small buttons lying over the PictureBox (thanks for the help with this problem :)).
Now I have to determine whether a button is inside the contour or outside. I already have an idea of how to do this:
In a For Next loop every button should be checked if it has been painted.
If yes, it is partially inside the contour.
If no I have to check whether one of all the buttons under this one (in y direction) is painted.
If none of the buttons below this one is painted then it is not inside the contour.
If only one button below is painted then it is inside the contour and if two buttons are painted then it is outside of the contour again.
The problem I have is the following:
How can I check if a button is painted or not?
All the small buttons are generated through the code. Can I actually write code to check these buttons by referring to their name when they don't even exist yet in the code?

Essentially it sounds like you want to check if a point is within a circle, mathematically speaking this would be the algorithm:
(point.x - center.x) ^ 2 + (point.y - center.y) ^ 2 < radius ^ 2
In terms of your two questions, the button would be painted after you create the control. Probably not immediately, but you can force the control to be repainted dynamically by refreshing the parent.
Since you're setting the Button's name, then you could use the Form.Controls.Find method and set the searchAllChildren parameter to True.

Related

Fill a Rectangular with an inflating Circle without crossing its borders (Ripple)

I have a control surface (my custom control) and a drawed rectangular smaller than the whole surface. Now I need to draw a circle (FillEllipse) within this rectangular and the circle must not overdraw the rectangular borders.
I made a ButtonStrip like this one know from Angular:
https://material.angular.io/components/button-toggle/overview
And want to create such ripples:
https://material.angular.io/components/ripple/examples
It is almost perfect. I would like to make such an animation. The filling progress is not a problem, but the fact, that I dont use kinda "child control buttons" in my ButtonStrip and therefore no "real" borders, I struggle with the not"over"drawing the borders of my clicked button.
And no, I will not use child buttons as controls to put into my strip. I dont want this control in control control. I am drawing the buttons and use the rectangulars as "address" for my events like Hover/Click/Clicked etc.
Here is an example of my ButtonStrip (Month is hovered). Day is clicked.
Every Button (in a List or Dictionary) has its own rectangular which is pre calculated:
Dim InnerWidth As Integer = If(_IOSwitchVisible = True,
(Me.Width - _IOSwitchWidth) \ Buttons.Count,
Me.Width \ Buttons.Count)
If InnerWidth = 0 Then Return
For Each b In Buttons.Values
b.Rect = New Rectangle(CurrLeft, 0, InnerWidth, Me.Height)
VerticalLinePos.Add(CurrLeft)
CurrLeft += InnerWidth
Next
This is only a part of the code.
And here is a snippet how I draw a clicked button (either hovered or not)
'Clicked
For Each btn In Buttons.Values.ToList.Where(Function(x) x.Clicked = True)
If _HoveredElement IsNot Nothing AndAlso _HoveredElement.Rect = btn.Rect Then
'If the button is already clicked
e.Graphics.FillRectangle(New SolidBrush(ClickedHoveredColor), btn.Rect)
Else
'Otherwise draw clicked color
e.Graphics.FillRectangle(New SolidBrush(ClickedColor), btn.Rect)
End If
Next
Does someone got an idea how to fill one of these buttons with a circle which must not overdraw to its neighbour buttons?
Edit: As soon as I (or you) found out how, I will include a loop to inflate the circle to animate a filling progress.
Thanks to Jimi I got it working with his Clip hint:
(The yellow circle is from ScreenToGif)
Private Sub AnimateRipple(G As Graphics, C As Color, R As Rectangle)
Dim ClipRegion As New Region(R)
G.SetClip(ClipRegion, Drawing2D.CombineMode.Replace)
Dim EllipseRect As Rectangle = R
EllipseRect.Width = 1
EllipseRect.Height = 1
EllipseRect.Y = R.Height \ 2
EllipseRect.X += R.Width \ 2
EllipseRect.Inflate(InflateValue, InflateValue)
Dim RippleBrush As SolidBrush = New SolidBrush(C)
G.FillEllipse(RippleBrush, EllipseRect)
G.ResetClip()
RippleBrush.Dispose()
End Sub

How to "refresh" graphics when it's moving around?

Say for example I draw a box on my screen. The box's X and Y coord will change pretty much all the time. When I am drawing the box and all of it's new position, a new box keeps appearing. I want to draw the same box, and as its location is changed, draw that same box on the new location.
Example :
Box1 : X/Y = 0,0
Box1 (new X/Y) = 0,15
I now have 2 box on my screen.
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
Dim doo As Integer = 1
While doo = 1
For i As Integer = 0 To MonsterCount
Dim xx As Integer = GetPrivateProfileInt("iPC=" & i, "X-Int:", 0, INI)
Dim yy As Integer = GetPrivateProfileInt("iPC=" & i, "Y-Int:", 0, INI)
Box(i) = New Box(xx, yy)
If Box(i).x > -10 And Box(i).y > -10 And Box(0).x <= 1920 And Box(0).y <= 1080 Then
Dim rect As New Rectangle(Box(i).x, Box(i).y, 120, 80)
e.Graphics.DrawRectangle(Pens.Green, rect)
Invalidate()
End If
Next i
Threading.Thread.Sleep(5)
End While
End Sub
That code is very wrong. You need to handle the Paint event of the control you want to draw on and just do your drawing as it should be at that instant. All the data that describes the drawing should be stored in member variables. Any code that changes what needs to be drawn should be outside the Paint event handler. Once it has made changes that need implementing, it should call Invalidate and specify the smallest area that it reasonably can. The next time the control is painted, the Paint event handler will update the drawing and then the invalidated area will be repainted. You might check this out for an example.
In your specific case, you should declare a member variable to store the data required for the box. If the size remains the same then all you need is a Point, otherwise you should keep a Rectangle. Each time the box needs to move, you should store the new value in your field and then call Invalidate twice. The first time you should specify the old Rectangle and the second time you should specify the new Rectangle. By doing that, you ensure that any area that may have changed will be repainted but the rest of the area, which can't have changed, won't be repainted. It's the actual painting to screen, not the drawing code, that is the slow part so you should try to keep that to a minimum. If you really need the repaint done immediately then you can call Update but, otherwise, the new drawing will be displayed the next time the UI thread is free to do so.

Location properties on winforms acting strange

I have a form where I allow the user to generate multiple panels with some various contents in the panels by pressing an "add" button. Depending on what the user does in the panel, the panel grows and shrinks to fit the contents. Because of this change is size, I have created a sub that formats the panels on the form.
Private Sub formatPanels(frm As Form)
Dim count As Integer = 0
Dim startPoint As Point = New Point(12, 80)
Dim endPoint As Point = New Point(0, 0)
Dim maxY As Integer = 0
For Each pnl As Control In frm.Controls
If TypeOf pnl Is Panel Then
ReDim Preserve _arr_Panels(count)
_arr_Panels(count) = pnl
count += 1
pnl.Location = startPoint
startPoint.Y += pnl.Size.Height + 30
End If
Next
End Sub
So as you can see, we loop through every panel and the first always begins at the location (12,80) and then increments with the size of the panel and some spacing.
HERE IS THE ISSUE. This ONLY happens when i am SCROLLED DOWN the form. The panels spacing all of a sudden screws up and decides to put the first panel hundreds of pixels down the form. Is the location property based on what you're looking at? So if I were scrolled down the form location(0,0) would be the top left of the current view? There must be some weird property to location that I am not aware of.
Thanks
This behavior is not related to a panel, but to any control on a form with AutoScroll = True and Anchor including Top. (Note: if Anchor didn't also include Left I had some strange positioning on the first call of the function.
The solution is described here which is to use AutoScrollPosition. If you change your startPoint to this it will adjust for the scroll position.
Dim startPoint As Point = New Point(12, Me.AutoScrollPosition.Y + 80)
And the documentation for AutoScrollPosition states this:
When adding controls programmatically to a form, use the AutoScrollPosition property to position the control either inside or outside of the current viewable scroll area.

VB.NET panel scrolling without showing scrollbars

I am trying to create a custom UI element: a panel, derived from the original panel, which is scrollable with special scrollbars (not the integrated ones) and has some other special abilities.
The actual problem is the scrolling. When I change the value of the custom scrollbar (e.g. scrolling), the panel-integrated scrollbars show up suddenly, although autoScroll = false.
Leading to the unintended state where both scrollbars are visible, the integrated and my custom one.
Private Sub ScrollB_EvValueChanged(NewVal As Integer) Handles ScrollB.EvValueChanged
Me.CleanPanel1.VerticalScroll.Value = NewVal
End Sub
How can I assign the new scrolling position (the new offset), determined by the custom scrollbar, to the panel without showing up panel-integrated scrollbars?
Sadly a panel (or usercontrol) with another panel on it and playing with the .Top and .Left properties of the inner panel to simulate scrolling is not an appropriate solution in my case.
Thank you for all your hints!
'I've been looking for methods of doing this all over, most of it is way more complicated than it needs to be, or you gotta write a whole dang program just to remove the bars to scroll. Anyhow, here's a quick, effective, neat method of doing this (since I was having trouble finding anything, I'll post it.
'e.delta detects a mouse wheel tick, if greater than 0, scroll up, less than 0 scroll down.
' the -91 part deducts some of the panel overhang (adjust as needed)
'I have buttons in this project that are 50 tall, so this scrolls perfectly for me.
'the nested if statements where there is no code (just else), tells the program to stop 'do nothing if a border is near by. The bottom of my panel, while scrolling, doesn't 'stop when the bottom of the panel (which is hanging off the bottom of the form quite 'some ways) reaches the bottom of the form, this can be adjusted by altering the 700 constant.
Private Sub DaddyPanel_MouseWheel(sender As Object, e As MouseEventArgs) Handles DaddyPanel.MouseWheel
If e.Delta < 0 Then
If (-DaddyPanel.Height - 91) > (DaddyPanel.Location.Y - 700) Then
Else
TextBox1.Text = DaddyPanel.Height & " " & DaddyPanel.Location.Y
DaddyPanel.Location = DaddyPanel.Location + New Point(0, -50)
End If
Else
If DaddyPanel.Location = New Point(0, 0) Then
Else
DaddyPanel.Location = DaddyPanel.Location + New Point(0, 50)
End If
End If
End Sub

Reversi VB.net logic behind it

I am trying to make the reversi game in VB.Net. I have some difficulties translating the game`s logic into vb.net
If a button is black and the button next to it is white,than the button next to the white one will be black wen pressed.
newButton.tag = colum of button + (row of button * amount of columns)
-> I made 64 buttons via a function loop and added a tag
Dim knop As Button = sender
Dim value As String = knop.Tag
If value = "...(?)" Then
knop.BackColor = Color.Black
If ....(?)
End If
End If
I already made a scheme with the label of the buttons, but I find it hard to implement the logic. Can someone help me out with thid one?
EDIT: http://i.stack.imgur.com/3gdrJ.png
If you use Dim ButtonList As List(Of List(Of Button)) and add the buttons to the form in runtime you can add each the button for each row to a list then add that list to ButtonList. Now you can access each button by the indexes in the 2 dimensional list.
Since you're changing the backcolor just use that instead of using the tag.