Non modal form that blocks its Owner - vb.net

I was trying to get forms configuration for my project that can suit my needs here and didn't get to a solution. It seem's I find a solution and now I have to implement it application-wide.
Here I have main form and a few subforms (which have many subforms) and through the main form I should ALWAYS be able to close all opened forms, watch for universal keypresses, close application permanently and watch for critical events.
I find solution in this facts:
From main form I open main subforms instantiated but non modally.
From sub forms I open deeper subforms also instantiated and non modally.
In all those subforms _Load event handler I disable calling form and in _FormClosing handler I enable it again. That way non modal forms act like modal forms in order to caller but not in order to main form which stays responsive all the time!
In forms that have to block its own caller (Owner) I added property "Blocking" so that my code looks like this:
If Not formIsOpened(frm_myFirstChild) Then
Dim f As New frm_myFirstChild
f.Blocking = True
f.Show(Me)
f = Nothing
End If
In the frm_myFirstChild I have property:
<Browsable(True), _
DefaultValue(False)> _
Public Property Blocking() As Boolean
Get
Return _Blocking
End Get
Set(ByVal value As Boolean)
_Blocking = value
End Set
End Property
If boolean property "Blocking" is TRUE then under _Load this code has to be executed:
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = False
End If
In _FormClosing that:
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
All of that work as expected so I try to implement that for all forms and use when needed in a subclass "cls_transform":
Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
MyBase.OnFormClosing(e)
End Sub
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = False
End If
MyBase.OnLoad(e)
End Sub
Here I have problem that subclass doesn't understand the property Blocked (not declared).
How do you get a Form's property Blocked to subclass so I can use those subclasses for all forms, and switch the property Blocking from the outside regarding functionality needs?

Sounds to me that "cls_transform" is actually a class derived from Form, the only way that OnFormClosing could work. Which is okay, your "sub-forms" now need to derive from cls_transform instead of Form. Do pick a better name.
Then simply add the Blocking property to that cls_transform class to solve your problem.
Do note that there's a bug in your OnFormClosing method. It can be canceled and that will leave the form opened with its owner in the wrong state. You need to write it like this instead:
Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
MyBase.OnFormClosing(e)
If Not e.Cancel And Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
End Sub

Related

Check whether a form is loaded (even if Minimized)

I have declared a form as
Private _fUpdate As frmUpdate
There are all kinds of solutions to check if a form is open and visible.
However, if a form is Minimized and not shown in the taskbar, it doesn't show up in Application.OpenForms.
Also Form.IsHandleCreated returns false with the above window state.
If Not uForm Is Nothing returns True even if the form has not yet been instanciated, so it isn't usuable as well.
Is there another way to check whether a form is loaded then a variable storing the window existance and hidden/shown state?
What I usually do if I need to get the instance of a form from somewhere else within my application, is create a Shared property in your form called Instance or something similar and set it in the form's Load event.
With this instance property, you can call it from anywhere else in your application, and use the WindowState property of your form to check its state.
Example of the form:
Public Class frmUpdate
Public Shared Property Instance As frmUpdate
Private Sub OnLoad(sender As Object, e As EventArgs) Handles MyBase.Load
Instance = Me
End Sub
End Class
Example usage in some other method:
Private Sub DoSomething()
If (frmUpdate.Instance.WindowState = FormWindowState.Minimized)
'Do something
End If
End Sub

Adding a User Control dynamically to a FlowLayoutPanel

I've created a UserControl and added it to a FlowLayoutPanel. The UserControl is being used to allow a user to enter a material, cost and delivery status to the form. When the user has filled it in, I want another UserControl to appear underneath it in the FlowLayoutPanel
The UserControl simply generates a string based on the text entered into two TextBox controls and the status of two Checkbox controls. it also has a property that indicates when the user has filled in enough information. I want to use this property to signal generating a new UserControl.
At the moment I have my first UserControl on the FlowLayoutPanel, it is successfully passing the String and CreateNew property back.
The problems I am encountering are:
How do I monitor to see if CreateNew has changed to True?
How do I add a control to the form and +1 to the controls name for future referencing
Once the new control is added, I need to monitor to see if the new CreateNew state changes to repeat the cycle
Can anyone point me in the right direction here, there's a lot of information out there on this, but I can't seem to find anything useful from other's problems/questions.
UPDATE 1
Thanks to user Zaggler for the comment, I have now managed to get the control to create a new instance of itself on the FlowLayoutPanel. But now I'm faced with a new problem of it only creating one new usercontrol, then it stops.
Here's the code I'm using:
UserControl Code
Public Class Alv_Product_Order_Control
Public OutString As String
Public Event CreateNew()
Dim CreateNewRaised As Boolean
Private Sub OutputString(sender As Object, e As EventArgs) Handles tbMaterial.TextChanged, tbCost.TextChanged,
cbDelivered.CheckedChanged, cbOrderPlaced.CheckedChanged
OutString = "¦¦" & tbMaterial.Text & "¦" & tbCost.Text & "¦"
If cbOrderPlaced.Checked = True Then
OutString = OutString & "Yes¦"
Else
OutString = OutString & "No¦"
End If
If cbDelivered.Checked = True Then
OutString = OutString & "Yes¦"
Else
OutString = OutString & "No¦"
End If
If tbCost.Text = "" Or tbMaterial.Text = "" Then
Else
If CreateNewRaised = False Then
RaiseEvent CreateNew() 'Raise the event that's used to signal adding a new control to the layout
CreateNewRaised = True 'Create A Latched Boolean that cannot change again in the future
End If
End If
End Sub
Public ReadOnly Property Alv_Product_Order_Control As String
Get
Return OutString 'Pass string back to main form
End Get
End Property
Main Form Code
Private Sub CreateSecondPOC() Handles POC1.CreateNew
FlowLayoutPanel1.Controls.Add(New Alveare_VB.Alv_Product_Order_Control)
End Sub
I'm guessing here that the problem is the CreateSecondPOC only handles the event for the the first POC1
How do I create a new Alveare_VB.Alv_Product_Order_Control, name it as POC2 and also add a handler to handle POC2.CreateNew and add another control?
Edit 2
I know I've found the answer, but i'd like to look at this memory leak that has been mentioned. I've changed the code supplied in the answer below to this:
Private Sub CreateSecondPOC(ByVal sender As Object, ByVal e As System.EventArgs) Handles POC1.CreateNew
Try
Dim oldPoc = DirectCast(sender, Alveare_VB.Alv_Product_Order_Control)
RemoveHandler oldPoc.CreateNew, AddressOf CreateSecondPOC
Catch ex As Exception
Debug.Print(ex.Message)
End Try
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
And I get the following error on the "RemoveHandler" line:
Unable to cast object of type 'System.Windows.Forms.TextBox' to type 'Alveare_VB.Alv_Product_Order_Control'.
The CreateNew event is raised when a textbox is written in, this is getting passed back as the sender I assume? Not really sure where to go with this now.
Edit 3
The error was in my UserControl, I was sending the incorrect object back (in this case the textbox). I have now changed the RaiseEvent to return the UserControl as an object. Now all is working correctly.
You could change your handler to something like this
Private Sub CreateSecondPOC() Handles POC1.CreateNew
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
I'm not sure if you want to keep handling the event, even after it has been populated once, i.e. can it be depopulated, then repopulated, then raise the event again? Maybe you want to lock it once it's populated, but this isn't clear.
You could also keep all your POC controls in a container and only create a new one when they are all populated.
Edit:
According to comments below, you should remove the event handler when you no longer need it in order to avoid memory leaks. Here is one way
Private Sub CreateSecondPOC(sender As Object) Handles POC1.CreateNew
Dim oldPoc = DirectCast(sender, Alveare_VB.Alv_Product_Order_Control)
RemoveHandler oldPoc, AddressOf CreateSecondPOC
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
But the last POC control's event handler would never be unsubscribed. So you could also do something like this when closing the form
For Each poc In Me.FlowLayoutPanel1.Controls.OfType(Of Alveare_VB.Alv_Product_Order_Control)()
RemoveHandler poc.CreateNew, AddressOf CreateSecondPOC
Next poc
I mentioned above that You could also keep all your POC controls in a container, which would be a better way to keep track of your controls, instead of using the FlowLayoutPanel as logical container. Just do what works for you and consider removing the handlers.

Unable To Get Public Property To Work In Custom Class

I have created a custom Class to which mimics a Textbox control but allows me to embed a button within this control. Everything works fine normally.
However my problem is that I am trying to setup a Public Property that allows me to turn this button visible or not (well create it actually), but I cannot seem to work out why my property never gets set to True - meaning that since I've implemented this piece of code, my button is no longer drawn anymore.
I have the following code:
Public Class CustomTextbox
Inherits TextBox
Private m_EnableSearch As Boolean
Private search_btn As Button
Public Sub New()
' This call is required by the designer.
Initialize()
End Sub
Private Sub Initialize()
search_btn = Nothing
'Draw the buttons
If EnableSearchButton = True Then CreateSearchButton() '<== This never equals True
End Sub
Private Sub CreateSearchButton()
search_btn = New Button()
...
...
End Sub
<Category("Appearance")> _
<Description("Enables the search button")> _
Public Property EnableSearchButton() As Boolean
Get
Return Me.m_EnableSearch
End Get
Set(value As Boolean)
Me.m_EnableSearch = value
Me.Invalidate()
End Set
End Property
End Class
If I take out the check for EnableSeachButton = True and just change it to CreateSearchButton, the button appears as expected. However, even though I can see and change the EnableSearchButton property in the design view, it never seems to equal true when I step through the code.
Any help appreciated, thanks!
Your question is a little confusing. Does EnableSearchButton actually ADD a button to the control or is it meant to toggle the Enabled state of said button? To get the button to respond to the Property and toggle the enabled state, you need to set the button's state:
Public Property EnableSearchButton() As Boolean
Get
Return Me.m_EnableSearch
End Get
Set(value As Boolean)
Me.m_EnableSearch = value
' to create the button when the prop is set:
If Value AndAlso search_btn IsNot Nothing Then
' create button
End If
If Value = False Then
' destroy button
End If
Me.Invalidate()
End Set
End Property
Since you appear to be creating the button at runtime, you will have to add a check for cases when it is nothing. Since the constructor only runs once and when created, there is no need to test the state of EnableSearchButton, just create it always.
If your Enabled property actually means something like AddButton to this TB, then just pass a Boolean in the ctor:
Public Sub New(boolAddButton As Boolean)
If boolAddButton Then CreateSearchButton()
End Sub
The way you have it, the backing field m_EnableSearch will always be true when the control is created. Any prop setting you do in the IDE happens after the control is created which is the only place you have it set up to create the button.

Decent way to requery CanExecute in Silverlight MVVM?

I have a simple LoginForm.
Here is how the code-behind looks like:
Private Sub btnLogin_Click(sender As Object, e As RoutedEventArgs) _
Handles btnLogin.Click
If Me.loginForm.ValidateItem() Then
'Do the actual login - (calling VM command)
DirectCast(Me.DataContext, LoginViewModel).LoginCommand.Execute()
End If
End Sub
Now I created a LoginViewModel that exposes a LoginCommand. I would like to keep the code-behind clean, and in the other hand, leave the ViewModel UI independent.
What should be the cleanest way to do this?
I am looking for an application level solution so I can make all the controls UpdateSourceTrigger=PropertyChanged or another workaround whatsoever to requery the CanExecute command when attempting click.
Update after Jon's answer:
So where should I call this method from, should it be the Login?
Private m_LoginCommand As ICommand
Public ReadOnly Property LoginCommand() As ICommand
Get
If m_LoginCommand Is Nothing Then m_LoginCommand =
New DelegateCommand(AddressOf Login, AddressOf CanLogin)
Return m_LoginCommand
End Get
End Property
Private Function CanLogin() As Boolean
Return Not IsLoggingIn
End Function
Private Sub Login()
DirectCast(LoginCommand, DelegateCommand).RaiseCanExecuteChanged()
If Not CanLogin() Then Exit Sub
'Do login
End Sub
It isn't completely clear what your goal is, so I hope I got this right.
Assuming you are using Prism, then whenever LoginCommand's can-execute status is changed (which is done from your ViewModel), the VM should immediately call RaiseCanExecuteChanged on it. This will notify all controls that bind to this command that they need to requery the CanExecute status.
If you are not using Prism, your command class should have some similar mechanism.
In any case, you don't need to do anything from the View.

Better way to kill a Form after the FormClosing event is overridden to hide rather than close?

I have a simple Windows Form that hosts property controls at runtime. When the user clicks Close [X] I want to keep the window and its contents alive rather than killing it by handling the FormClosing event, canceling the event and simply hiding the form.
That's fine but at close of the application I need to actually close the window. I implemented the below but it feels kludgey. Is there a simpler, more clever way to handle this situation? (The form's controller calls KillForm explicitly after it receives a closing event from the main window.)
Friend Class HostForm
Private _hideInsteadOfClosing As Boolean = True
Private Sub HostForm_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) _
Handles Me.FormClosing
If _hideInsteadOfClosing Then
Me.Hide()
e.Cancel = True
End If
End Sub
Public Sub KillForm()
_hideInsteadOfClosing = False
Me.Close()
End Sub
End Class
You can examine the value of the CloseReason property of the event args object. If it is UserClosing, hide the form, otherwise close it. For all possible values of this property, check the CloseReason enumeration documentation.