AddHandler from Page1 to Show Message MainWIndow using WPF and VB.NET - wpf

I'm stuck. I am trying to AddHandler for a button on Page1 such that when the button is clicked it will AddHandler to the MainWindow_MouseUp event, so I can click anywhere on the MainWindow to show a message.
How can I fix this problem?
Notes:
I have removed the Handler for MainWindow_MouseUp on MaindWindow_load event, so the message will no show on load of MainWindow.
I have a frame showing Page1.
When a button in Page1 is clicked, it's supposed to AddHandler, so MainWindow_MouseUp will show a message when MainWindow executes the MainWindow_MouseUp event.
Page1.xaml
<Page x:Class="Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1" Background="Red">
<Grid>
<Button Content="AddHandler for MainWindow MouseUp message"
Height="42"
HorizontalAlignment="Left"
Margin="0,126,0,0"
Name="Button1"
VerticalAlignment="Top"
Width="300" />
<TextBlock Height="84"
HorizontalAlignment="Left"
Margin="80,184,0,0"
Name="TextBlock1"
Text="The button will add a handler in order to show a message on the MainWindow MouseUp event"
VerticalAlignment="Top"
Width="151"
TextWrapping="Wrap" />
<TextBlock Height="23"
HorizontalAlignment="Left"
Margin="12,56,0,0"
Name="TextBlock2"
Text="Page1"
VerticalAlignment="Top" />
</Grid>
</Page>
Page1.vb
Class Page1
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs) _
Handles Button1.Click
Dim _MainWindow As New MainWindow
AddHandler _MainWindow.MouseUp, AddressOf Button1_Click
End Sub
End Class
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Frame Source="Page1.xaml"
Height="300"
HorizontalAlignment="Left"
Margin="110,-1,0,0"
Name="Frame1"
VerticalAlignment="Top"
Width="300" />
<TextBlock Height="144"
HorizontalAlignment="Left"
Margin="0,48,0,0"
Name="TextBlock2"
Text="Click here to show a message if handler MainWindow MouseUp is added from page1"
VerticalAlignment="Top"
TextWrapping="Wrap"
Width="104"
Foreground="#FF003AFF" />
<TextBlock Height="153"
HorizontalAlignment="Left"
Margin="416,159,0,0"
Name="TextBlock3"
Text="Click here to show a message if handler MainWindow MouseUp is added from page1"
VerticalAlignment="Top"
TextWrapping="Wrap"
Foreground="#FFEF00FF" />
</Grid>
</Window>
MainWindow.vb
Public Class MainWindow
Private Sub Window_Loaded(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs) _
Handles MyBase.Loaded
RemoveHandler Me.MouseUp, AddressOf MainWindow_MouseUp
End Sub
Private Sub MainWindow_MouseUp(ByVal sender As Object, _
ByVal e As MouseEventArgs) _
Handles Me.MouseUp
Test1()
End Sub
Public Sub Test1()
MessageBox.Show("AddHandler added from page 1 was successful!")
End Sub
End Class

This is just all a bit confused. Let's look at what you are doing :
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As _
System.Windows.RoutedEventArgs) Handles Button1.Click
Dim _MainWindow As New MainWindow
AddHandler _MainWindow.MouseUp, AddressOf Button1_Click
End Sub
This is making a new MainWindow and is linking its MouseUp event to the method Button1_Click - you're telling _MainWindow to execute Page1's Button1_Click method when it fires its MouseUp event. I suspect this is not what you want to do.
Now this :
RemoveHandler Me.MouseUp, AddressOf MainWindow_MouseUp
tells me that you don't want MainWindow_MouseUp to be hooked up right away. You can get rid of this line simply by removing the Handles clause like :
Private Sub MainWindow_MouseUp(ByVal sender As Object, _
ByVal e As MouseEventArgs) 'Handles Me.MouseUp
Test1()
End Sub
Now there's nothing to remove.
So... we're back to the problem of notifying MainWindow that Page1's button has been clicked. To do this, add an event to Page1 :
Class Page1
Shared Event PageButtonClick(sender As Object, e As RoutedEventArgs)
Private Sub Button1_Click(sender As System.Object, e As _
System.Windows.RoutedEventArgs) Handles Button1.Click
RaiseEvent PageButtonClick(sender, e)
End Sub
End Class
I've made the event shared here - this allows us to link it up without creating an instance of the page in MainWindow (ie: you can simply have a frame using Page1 as source). What we're doing here is raising a PageButtonClick event in Page1 when Button1 has been clicked.
In your main window, then :
Class MainWindow
Private Sub Window_Loaded(sender As System.Object, e As _
System.Windows.RoutedEventArgs) Handles MyBase.Loaded
'Here we listen for the Page1Click event and execute the method
'Page1_Clicked when we hear it
AddHandler Page1.PageButtonClick, AddressOf Page1_Clicked
End Sub
Private Sub Window_MouseUp(sender As System.Object, e As _
System.Windows.Input.MouseButtonEventArgs)
MessageBox.Show("Hello")
End Sub
Private Sub Page1_Clicked(sender As Object, e As RoutedEventArgs)
'When we get a click from Page1, add a handler to this window's
'MouseUp event and have it execute the Sub Window_Mouseup
AddHandler Me.MouseUp, AddressOf Window_MouseUp
End Sub
End Class
Now, the above is quite dangerous because you'll always be adding handlers any time the button is clicked. You should always be very careful about adding handlers - whenever you add one you MUST make sure to remove it before freeing up whatever has hooked it up (otherwise you end up with memory leaks since handlers can root an object!). I would always do something like this (in MainWindow) :
Private _mouseUpConnected As Boolean = False
Private Sub Page1_Clicked()
If Not _mouseUpConnected Then
AddHandler Me.MouseUp, AddressOf Window_MouseUp
_mouseUpConnected = True
End If
End Sub
This makes sure you only add the handler once. Now to get rid of it later :
Private Sub Window_Closed(sender As System.Object, e As System.EventArgs) _
Handles MyBase.Closed
RemoveHandler Page1.PageButtonClick, AddressOf Page1_Clicked
if _mouseupConnected then RemoveHandler Me.MouseUp, AddressOf Window_MouseUp
End Sub
This takes care of the PageButtonClick handler - it is added when the window is loaded and removed when it is closed. It also takes care of the MouseUp handler - if it has been connected, we disconnect it before closing. This ensures that MainWindow is properly garbage collected.

Related

How do I change the value of a bound WPF class variable?

I am new to WPF/XAML.
I am making a scrolling marquee using a demo I found online.
It is WinForms using a HostedElement WPF custom User Control.
I was able to setup a class called MyModelView so that I can bind the text value of a textblock to a variable I control (MyTextProperty).
But I can't figure out how to change the variable. My goal is to have a textbox you can enter text into and it will bind to the textblock value and change it while the program is running.
I am not able to do this, which I am trying to do (Change bound variable to TextBox1 value - it is not a valid reference to the variable... what is?):
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
MyViewModel.MyTextProperty = TextBox1.text
End Sub
How do I reference that class variable to change the text? I am missing something.
MyViewModel.vb
Imports System.ComponentModel
Public Class MyViewModel
Implements INotifyPropertyChanged
Public Sub New()
Me.myTextValue = "WINNING!!!"
End Sub
Private myTextValue As String = String.Empty
Public Property MyTextProperty() As String
Get
Return Me.myTextValue
End Get
Set(ByVal value As String)
Me.myTextValue = value
NotifyPropertyChanged("MyTextProperty")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Scrolling Marque.xaml
<UserControl x:Class="ScrollingMarquee"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:interopdemo="clr-namespace:InteropDemo"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<UserControl.Resources>
<Storyboard x:Key="MarqueeScroll">
<DoubleAnimation RepeatBehavior="Forever"
Storyboard.TargetProperty="(Border.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="spMarquee"
From="1500" To="-500"
Duration="0:0:0:7" />
</Storyboard>
</UserControl.Resources>
<Grid>
<Grid.DataContext>
<interopdemo:MyViewModel/>
</Grid.DataContext>
<StackPanel x:Name="spMarquee" Orientation="Horizontal" Width="Auto">
<TextBlock Text="{Binding MyTextProperty}" FontSize="28" VerticalAlignment="Center" Margin="30,0,60,0"/>
<TextBlock Text="Hello Scrolling Text!" Foreground="Firebrick" FontSize="28" VerticalAlignment="Center" Margin="40,0,60,0"/>
<TextBlock Text="Ticker 2000!" Foreground="Red" FontSize="28" FontStyle="Italic" FontWeight="Bold" VerticalAlignment="Center" Margin="50,0,80,0"/>
<StackPanel.RenderTransform>
<TranslateTransform/>
</StackPanel.RenderTransform>
</StackPanel>
</Grid>
</UserControl>
Form1.vb
Imports System.Windows.Media.Animation
Public Class Form1
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
Me.WindowState = FormWindowState.Maximized
ElementHost1.Visible = True
Dim storyRunMarquee As Storyboard = ScrollingMarquee1.FindResource("MarqueeScroll")
storyRunMarquee.Begin()
End Sub
End Class
I've tried referencing the variable but can't make it work. The scrolling animation displays as "Winning! Hello Scrolling Text! Ticker 2000!" But I'm trying to change the bound "Winning!" text.
Remove
<Grid.DataContext>
<interopdemo:MyViewModel/>
</Grid.DataContext>
Declare a member variable in the form to hold reference to the view model object:
Private scrollerViewModel As MyViewModel
Instantiate an object of class MyViewModel in Form1_Load and set it as a DataContext for user control.
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
Me.WindowState = FormWindowState.Maximized
ElementHost1.Visible = True
scrollerViewModel = New MyViewModel
ScrollingMarquee1.DataContext = scrollerViewModel
Dim storyRunMarquee As Storyboard = ScrollingMarquee1.FindResource("MarqueeScroll")
storyRunMarquee.Begin()
End Sub
Modify property:
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
scrollerViewModel.MyTextProperty = TextBox1.text
End Sub
I think you just need to cast the datacontext of ScrollingMarquee1 as MyViewModel
CType(ScrollingMarquee1.DataContext, MyViewModel).MyTextProperty = TextBox1.text

Event not fired .NET / XAML

I have a button and when it is clicked, an event should be raised. I declare the event in one class, but I invoke it in another.
Here is my code:
XAML:
<Button x:Name="btnAddUsers" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,15,60" Padding="5,0,5,0" Click="ImportButton_Click" Content="Add"></Button>
Event declaration and the Click method:
Public Event GettingADUsers()
Private Sub ImportButton_Click(sender As Object, e As RoutedEventArgs) Handles btnADAddUsers.Click
RaiseEvent GettingADUsers()
End Sub
In another class I invoke the event (I invoke it in the constructor):
AddHandler StaffSettings.GettingADUsers, AddressOf ctlStaff_GettingAllUsers
And the method:
Private Sub ctlStaff_GettingAllUsers()
adUsers.Load()
End Sub
Do I miss something?

Closing just one expander within another expander in WPF/ VB.NET

First post here so sorry if I mess something up/forget something.
I'm working in Visual Studio 2010 with a WPF application and am using VB.NET.
So I have a parent expander that contains a grid with two controls: another expander and another grid. I have written some code behind to cause each expander to become invisible when it is collapsed or closed (among a few other things), and to change color/ become visible when it is expanded/opened. I also have a few buttons placed in other areas that accomplish the same tasks. My problem is that when I collapse the 2nd expander, the 1st (parent) expander also closes/ becomes invisible. However, a button that is used to collapse the 2nd expander works perfectly. Here is my relevant code (hopefully I'm formatting this right):
XAML
<Expander Name="Expander1" Visibility="Hidden" >
<Grid >
<Grid.RowDefinitions >
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Expander Name="Expander2" Visibility="Hidden" >
<Content ...>
</Expander>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Content... />
</Grid>
</Grid>
</Expander>
VB.NET
Private Sub Expander2_Expanded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander2.Expanded
Expander2.Background = Brushes.PaleTurquoise
Expander2.BorderBrush = Brushes.Black
End Sub
Private Sub Expander2_Collapsed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander2.Collapsed
Expander2.IsExpanded = False
Expander2.Background = Brushes.Transparent
Expander2.BorderBrush = Brushes.Transparent
Expander2.Visibility = Windows.Visibility.Visible
ButtonA7.Visibility = Windows.Visibility.Visible
Expander1.IsExpanded = True
Expander1.Background = Brushes.PaleTurquoise
Expander1.BorderBrush = Brushes.Black
Expander1.Visibility = Windows.Visibility.Visible
End Sub
Private Sub Expander1_Expanded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander1.Expanded
Expander1.Background = Brushes.PaleTurquoise
Expander1.BorderBrush = Brushes.Black
End Sub
Private Sub Expander1_Collapsed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander1.Collapsed
Expander1.Background = Brushes.Transparent
Expander1.BorderBrush = Brushes.Transparent
Expander1.Visibility = Windows.Visibility.Hidden
ButtonA7.Visibility = Windows.Visibility.Visible
End Sub
Dont worry about all the buttons in the code, the buttons all work fine. In fact, one button is suppose to do the EXACT same thing as collapsing the expander, and it works properly. I just need the same thing to happen when you click on the actual expander to collapse it. Here is the code for the button so you see it is the same:
Private Sub Button_Click_2(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Expander2.IsExpanded = False
Expander2.Background = Brushes.Transparent
Expander2.BorderBrush = Brushes.Transparent
Expander2.Visibility = Windows.Visibility.Visible
Expander1.IsExpanded = True
Expander1.Background = Brushes.PaleTurquoise
Expander1.BorderBrush = Brushes.Black
Expander1.Visibility = Windows.Visibility.Visible
ButtonA7.Visibility = Windows.Visibility.Visible
End Sub
Thank you so much for any help, I really appreciate it!
EDIT: Alternatively, if there is an easy way (I am VERY new to WPF... ~1week) to hide/ get rid of the header, that could work too. But I would prefer it the other way where I tried before, if possible. Thanks!
Expander.Collapsed Event has routing strategy Bubbling. Mark event as handled before it reaches Expander1 and all should be okay.
Private Sub Expander2_Collapsed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Expander2.Collapsed
...
e.Handled = True
End Sub

Selected Index Change on WPF combobox?

I'm converting an app of mine from WinForms to WPF. I came across this problem where in WinForms if I CLEAR a combobox, that does not fire combobox.selectedindexchange. It only fires when the user actually changes the index.
That's the functionality I need. However in WPF I can only find combobox.SelectionChanged. This unfortunately fires on both index change and clearing a combobox (any change).
In WPF how can I only trigger an event when the user changes the index? I'm assuming there's a solution I'm missing that's like the WinForms solution. I'm trying not to play games with global variables and having to track what was previously selected....that's a mess.
Mouseleave is also a mess.
I would greatly appreciate any help!
In your case you can implement in SelectionChanged event:
Private Sub OnSelectionChanged(sender As System.Object, e As System.Windows.Controls.SelectionChangedEventArgs)
Dim combo = TryCast(sender, ComboBox)
If combo.SelectedItem IsNot Nothing Then
'do something
End If
End Sub
You can remove event handler, invoke clear method and add again event handler.
Example code:
<StackPanel>
<ComboBox Name="myCB"
SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem>test1</ComboBoxItem>
<ComboBoxItem>test2</ComboBoxItem>
<ComboBoxItem>test3</ComboBoxItem>
</ComboBox>
<Button Content="Clear" Click="Button_Click" />
</StackPanel>
MainWindow class:
Class MainWindow
Private Sub ComboBox_SelectionChanged(sender As System.Object, e As System.Windows.Controls.SelectionChangedEventArgs)
Console.WriteLine("Selected index: {0}", CType(sender, ComboBox).SelectedIndex)
End Sub
Private Sub Button_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
RemoveHandler Me.myCB.SelectionChanged, AddressOf ComboBox_SelectionChanged
Me.myCB.Items.Clear()
AddHandler Me.myCB.SelectionChanged, AddressOf ComboBox_SelectionChanged
End Sub
End Class

Update UI when property is changed

I am quite new to WPF and I am confused about how Data Bindings should behave.
I have created a class that has 1 property ("status") and three methods that are responsible for changing the status. This class implements the INotifyPropertyChanged interface so that I am able to notify the calling code when the Status changes.
The class looks like this:
Public Class StreetLight
Implements System.ComponentModel.INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _status As String
Public Property Status As String
Get
Return _status
End Get
Set(ByVal value As String)
_status = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Satus"))
End Set
End Property
Public Sub New()
_status = "unknown"
End Sub
Public Sub Red()
Status = "Red"
End Sub
Public Sub Yellow()
Status = "Yellow"
End Sub
Public Sub Green()
Status = "Green"
End Sub
End Class
I have created a WPF User Control to represent this class.
This User Control is bound to an instance the StreetLight class. It displays the status of the StreetLight and allows the user to change the status using buttons:
<UserControl x:Class="StreetLightUC"
x:Name="StreetLightUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:twpf="clr-namespace:TryingWPF"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="360">
<UserControl.Resources>
<twpf:StreetLight x:Key="theLight" PropertyChanged="theLight_PropertyChanged" />
</UserControl.Resources>
<StackPanel x:Name="StreetLightContent" Orientation="Horizontal">
<Label Width="100" HorizontalAlignment="Left">Street Light _Status</Label>
<Label x:Name="streetLightValue" Width="120" HorizontalAlignment="Right" Content="{Binding Path=Status, Mode=OneWay}"></Label>
<Button x:Name="Red" Click="TurnRed" Width="60">Turn Red</Button>
<Button x:Name="Green" Click="TurnGreen" Width="60">Turn Green</Button>
</StackPanel>
</UserControl>
My problem is that even when the status is changed for theLight, it is not updated in the Label that is bound to the Status property unless I create a new StreetLight and set the DataContext to this new instance in the "StreetLight_PropertyChanged" event that handles the PropertyChagned event for the "theLight"
Like so:
Public Class StreetLightUC
Public Sub New()
InitializeComponent()
End Sub
Private Sub TurnRed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim light As StreetLight= CType(FindResource("theLight"), StreetLight)
light.Red()
End Sub
Private Sub TurnGreen(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim light As StreetLight = CType(FindResource("theLight"), StreetLight)
light.Unlock()
End Sub
Private Sub theLight_PropertyChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
Dim light As StreetLight = CType(sender, StreetLight )
Dim newLight As New StreetLight
newLight.Status = light.Status
StreetLightContent.DataContext = newLight
End Sub
End Class
Am I doing something wrong?
It doesn't seem like I should have to create a new instance of the class to display the updated status-property when this property is change....
Thanks,
-Frinny
You have a typo ("Satus" instead of "Status"):
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Satus"))
Should be:
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Status"))
With this typo, the binding doesn't see that "Status" has changed, and never updates. If you correct this, the PropertyChanged event will correctly reflect that "Status" has changed.

Resources