TextBox MouseDown Event within UserControl - wpf

I have a user control that contains a TextBox and I would like to capture the mousedown event however, I cannot seem to get things to work! My existing, non working code is below, any assistance would be greatly appreciated.
UserControl xaml:
<UserControl x:Class="LeftLabel"
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" Width="auto" Height="auto" >
<StackPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=TextBlockText}"
Name="UcTextBlock"
Width="{Binding Path=TextBlockWidth}"
FontSize="{Binding Path=TextBlockFontSize}"
HorizontalAlignment="Right"
TextAlignment="Right"
VerticalAlignment="Center"
Margin="5,0,0,0" />
<TextBox Text="{Binding Path=TextBoxText}"
Name="UcTextBox"
MouseDown="UcTextBox_MouseDown"
Width="{Binding Path=TextBoxWidth}"
Height="{Binding Path=TextBoxHeight}"
FontSize="{Binding Path=TextBoxFontSize}"
Padding="{Binding Path=TextBoxPadding}"
Margin="5,0,0,0"
BorderThickness="0" />
</StackPanel>
</StackPanel>
</UserControl>
UserControl.vb:
Public Event TextBoxMouseDown As EventHandler
Private Sub UcTextBox_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles UcTextBox.MouseDown
RaiseEvent TextBoxMouseDown(sender, e)
End Sub
For testing purposes I am adding the UserControls to my MainWindow programmatically:
Dim count As Integer = 1
While count < 10
Dim ucl As New LeftLabel
With ucl
.Margin = New Thickness(4)
.TextBlockText = "Label " & count.ToString
.TextBlockWidth = 100
.TextBlockFontSize = 12
.TextBoxFontSize = 12
.TextBoxHeight = 20
.TextBoxText = "Initial Text " & count.ToString
.TextBoxPadding = New Thickness(2)
.TextBoxWidth = 150
AddHandler .TextBoxMouseDown, AddressOf LabelLeftTextBoxMouseDown
End With
TextBoxStackPanel.Children.Add(ucl)
count += 1
End While
Private Sub LabelLeftTextBoxMouseDown(sender As Object, e As EventArgs)
Dim txt As TextBox = DirectCast(sender, TextBox)
MsgBox(txt.Text)
End Sub

This is a somewhat common problem.
It occurs with some controls due to the fact that these controls handle these events internally. The button, for instance, "swallows" the click and rather exposes its own event - the Click-event.
If you want to declare your textbox-event-handlers in XAML, I suggest you checkout the Preview*-events (i.e. PreviewMouseDown), these always occur, maybe that can solve your problem if you need to react to clicks.
<TextBox Text="{Binding Path=TextBoxText}"
Name="UcTextBox"
PreviewMouseDown="UcTextBox_PreviewMouseDown"
Width="{Binding Path=TextBoxWidth}"
Height="{Binding Path=TextBoxHeight}"
FontSize="{Binding Path=TextBoxFontSize}"
Padding="{Binding Path=TextBoxPadding}"
Margin="5,0,0,0"
BorderThickness="0" />

Related

Click a Button from UserControl to set a value to MainWindow's columndefinition

I want to write a code from UserControl's Button to set value to MainWindow's ColumnDefinition.
When clicked button I need change to this:
<ColumnDefinition Width="0" MaxWidth="400" MinWidth="10" x:Name="MainMenu" x:FieldModifier="public" />
Below is my code:
MainWindow.xaml
<Grid Margin="0,0,0,0">
<local:DockPanelTop />
<Grid Margin="5,45,5,25" x:Name="MainGrid" x:FieldModifier="public">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" MaxWidth="400" MinWidth="10" x:Name="MainMenu" x:FieldModifier="public" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
DockPanelTop.xaml
<UserControl x:Class="DockPanelTop"
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:local="clr-namespace:WpfApp4"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DockPanel LastChildFill="False" VerticalAlignment="Top" Background="Gray" Margin="0,0,0,0" Height="40">
<Button x:Name="HideMenu" Content="Hidden
Menu" Width="50" Margin="5" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="9" />
</DockPanel>
</UserControl>
DockPanelTop.xaml.vb
Public Class DockPanelTop
Private Sub HideMenu_Click(sender As Object, e As RoutedEventArgs) Handles HideMenu.Click
("When clicked set value to columndefinition width to "0" ")
End Sub
End Class
I find nothing the code sample on web in VB to the DockPanelTop.xaml.vb :( Please help me.
Sry, bad English.TY.
You can get a reference to the parent window using the shared Window.GetWindow method. Try this:
Private Sub HideMenu_Click(sender As Object, e As RoutedEventArgs)
Dim window = TryCast(System.Windows.Window.GetWindow(Me), MainWindow)
If (window IsNot Nothing) Then
Dim columnDef = window.MainGrid.ColumnDefinitions(0)
Dim width = New GridLength(0)
columnDef.Width = New GridLength(0)
columnDef.MinWidth = 0.0
End If
End Sub
My Answer:
Public Sub HideMenu_Click(sender As Object, e As RoutedEventArgs) Handles HideMenu.Click
Dim window = TryCast(System.Windows.Window.GetWindow(Me), MainWindow)
Dim columnDef0 = window.MainGrid.ColumnDefinitions(0)
Dim columnDef0Width = window.MainGrid.ColumnDefinitions(0).Width
If (window IsNot Nothing) Then
If columnDef0Width.Value <= 10 Then
columnDef0.Width = New GridLength(150)
columnDef0.MinWidth = 10.0
ElseIf columnDef0.Width.Value > 10 Then
columnDef0.Width = New GridLength(10)
columnDef0.MinWidth = 10.0
End If
End If
End Sub

How do I add, save and load items to a listview in WPF

About two weeks ago I started to develop in WPF and, since I only developed in WinForms, I ran into common problems but managed to find the solution for them. However, currently I'm stuck at something: adding items with multiple columns (via visual basic code, not xaml) into a listview.
I'm not sure if it's best to use a Listview or a DataGrid control for this but basically I want the user to be able to add favourite songs to a list, save them to a file and load the file whenever the app is opened. The listview currently has three columns: Artist, Song and Status.
When I was programming in WinForms, I used to do this:
Dim Song As New ListViewItem
Form1.ListView1.Items.Add(Song)
Song.Text = TextBox1.Text
Song.SubItems.Add(TextBox2.Text)
Song.SubItems.Add(TextBox3.Text)
Then, to save:
Dim Song As New ListViewItem
Form1.ListView1.Items.Add(Song)
Song.Text = TextBox1.Text
Song.SubItems.Add(TextBox2.Text)
Song.SubItems.Add(TextBox3.Text)
Try
Dim myWriter As New IO.StreamWriter(PATH_DATABASE)
For Each myItem As ListViewItem In Form1.ListView1.Items
myWriter.WriteLine(myItem.Text & "|" & myItem.SubItems(1).Text & "|" & myItem.SubItems(2).Text & "|" & myItem.SubItems(3).Text
Next
myWriter.Close()
Catch ex As Exception
MsgBox("Error: " & ex.Message, vbCritical, "Error")
End Try
I've been searching and I've found that I need to use binding so I've tried this:
Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Windows
Public Structure Song
Public _artist As String
Public _title As String
Public _status As String
Property Artist() As String
Get
Return _artist
End Get
Set(ByVal Value As String)
_artist = Value
End Set
End Property
Property Title() As String
Get
Return _title
End Get
Set(ByVal Value As String)
_title = Value
End Set
End Property
Property Status() As String
Get
Return _status
End Get
Set(ByVal Value As String)
_status = Value
End Set
End Property
End Structure
Public Class WINDOW_AddSong
Dim songs As New ObservableCollection(Of Song)
Private Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click
Dim Song As New ListViewItem
For Each wnd As Window In Application.Current.Windows
If wnd.GetType Is GetType(MainWindow) Then
DirectCast(wnd, MainWindow).Listview1.Items.Add(Alimento)
Alimento.Content = New Song() With {._artist = "Lol", ._title = "Lol2", ._status = "Lol3"}
End If
Next
End Sub
End Class
And in the XAML listview:
<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Artist}"/>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status}"/>
This works however I'm not sure if this is the WPF-way of doing things.
However, I'm stuck at the saving process:
Try
Dim myWriter As New IO.StreamWriter(PATH_DATABASE)
For Each wnd As Window In Application.Current.Windows
If wnd.GetType Is GetType(MainWindow) Then
For Each myItem As ListViewItem In DirectCast(wnd, MainWindow).Listview1.Items
myWriter.WriteLine(Song, Artist, Status)
Next
End If
Next
myWriter.Close()
Catch ex As Exception
MsgBox("Error: " & ex.Message, vbCritical, "Error")
End Try
This doesn't work.
Also, PATH_DATABASE is just a directory.
To summarise, could an expert review my code and check if I'm doing things "right" and, if possible, could you help me with the saving and loading process?
Thanks.
I just updated your structure (I used a class, just force of habit) to use automatic property values which are available in recent versions of VS. The default implementation of properties is built in along with the backer fields. It is used just as before.
You are on the right track with ObservableCollection but you you need to implement INotifyPropertyChanged by adding the Event PropertyChanged and providing the Sub OnPropertyChanged to raise the event.
Set up the bindings on the controls and set the ItemsSource ot the ListView to your ObservableList.
To save Import System.Text so you can use a StringBuilder. The loop through the list to build the string to save to a text file.
I didn't spend any time trying to make the XAML Windows look nice. Pardon the ugliness.
Class MainWindow
Public Songs As New ObservableCollection(Of Song)
Protected Sub OnLoad(sender As Object, e As RoutedEventArgs)
Songs.Add(New Song("Nirvana", "Smells Like Teen Spirit", "Open"))
Songs.Add(New Song("John Lennon", "Imagine", "In Stock"))
Songs.Add(New Song("U2", "One", "Unknown"))
Songs.Add(New Song("Michael Jackson", "Billie Jean", "Open"))
lvSongs.ItemsSource = Songs
End Sub
Private Sub BtnAdd_Click(sender As Object, e As RoutedEventArgs)
Dim frm As New frmAdd
frm.mainForm = Me 'send the instance of the current form to the new form
frm.ShowDialog()
End Sub
Private Sub SaveList()
Dim sb As New StringBuilder
For Each item As Song In Songs
sb.AppendLine($"{item.Artist}|{item.Title}|{item.Status}")
Next
'call sb.ToString and write it to a .txt file
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF_BindComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Loaded="OnLoad"
Name="root">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView Margin="10" Name="lvSongs" Grid.Row="0">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBox x:Name="txtArtist"
Text="{Binding Artist, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontWeight="Bold"/>
<TextBlock Text=", " />
<TextBlock Text="Age: " />
<TextBox Text="{Binding Title}"
FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBox Text="{Binding Status}" />
<TextBlock Text=")" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="btnAdd" Grid.Row="1" Height="25" Width="100" Content="Add a Song" Click="BtnAdd_Click"/>
</Grid>
</Window>
The Song class
Public Class Song
Implements INotifyPropertyChanged
Public Property Artist() As String
Public Property Title() As String
Public Property Status() As String
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Sub New(art As String, Ttl As String, Stat As String)
Artist = art
Title = Ttl
Status = Stat
End Sub
End Class
The Add Window
Public Class frmAdd
Public mainForm As MainWindow
Private Sub BtnAdd_Click(sender As Object, e As RoutedEventArgs)
mainForm.Songs.Add(New Song(txtArtist.Text, txtTitle.Text, txtStatus.Text))
ClearForm()
End Sub
Private Sub ClearForm()
txtArtist.Clear()
txtTitle.Clear()
txtStatus.Clear()
End Sub
End Class
Add Window XAML
<Window x:Class="frmAdd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF_BindComboBox"
mc:Ignorable="d"
Title="frmAdd" Height="172.641" Width="307.547">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Artist" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtArtist" Grid.Column="1" Grid.Row="0" FontSize="16" Width="150" />
<TextBlock Grid.Column="0" Grid.Row="1" Text="Title" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtTitle" Grid.Column="1" Grid.Row="1" FontSize="16" Width="150"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Status" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtStatus" Grid.Column="1" Grid.Row="2" FontSize="16" Width="150"/>
<Button x:Name="btnAdd" Grid.Column="1" Grid.Row="4" Content="Add Song" Click="BtnAdd_Click"/>
</Grid>
</Window>
EDIT
Required Imports
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Text

Calculate a ScrollViewer's scrollbar offsets so that an object will be centered, while maintaining a layout transform who's scale is variable

I have been fighting with this one for many hours now, and I just can't seem to arrive at an acceptable answer. I am hoping someone out there with much stronger geometry skills than my self can solve this riddle for me. Any help would be greatly appreciated. The nature of my problem, and description is below in the image that I provided.
And here is a sample project that I have built, which does not correctly fulfill the requirements.
XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Center and Zoom ScrollViewer Test" Height="600" Width="800" WindowStartupLocation="CenterScreen">
<Grid>
<DockPanel>
<GroupBox Header="Parameters" DockPanel.Dock="Top" Margin="10">
<StackPanel Orientation="Horizontal">
<GroupBox Header="Manually Set ScrollBar Positions" Margin="10">
<StackPanel Orientation="Horizontal">
<TextBox Name="EditHorz" Width="60" Margin="10" TextChanged="EditHorz_TextChanged" />
<Label Content="x" Margin="0 10 0 10" />
<TextBox Name="EditVert" Width="60" Margin="10" TextChanged="EditVert_TextChanged" />
</StackPanel>
</GroupBox>
<GroupBox Header="Scale" Margin="10">
<DockPanel>
<Label Content="{Binding ElementName=scaleValue, Path=Value}" DockPanel.Dock="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="40" />
<Slider Name="scaleValue" Minimum="1" Maximum="4" SmallChange="0.05" LargeChange="0.1" Width="200" VerticalAlignment="Center" />
</DockPanel>
</GroupBox>
</StackPanel>
</GroupBox>
<GroupBox Header="Debug Output" Margin="10">
<TextBox Name="text" FontFamily="Courier New" FontSize="12" DockPanel.Dock="Left" Width="500" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" Margin="10" />
</GroupBox>
<GroupBox Header="Proof" Margin="10">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Width="60" HorizontalAlignment="Left" Content="Center" Click="ButtonCenter_Click" Margin="10" />
<Button Width="60" HorizontalAlignment="Left" Content="Reset" Click="ButtonReset_Click" Margin="10" />
</StackPanel>
<ScrollViewer Name="scroll" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" Background="Green" Width="100" Height="100" VerticalAlignment="Top" Margin="10">
<Canvas Width="200" Height="200" Background="Red">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=scaleValue, Path=Value}" ScaleY="{Binding ElementName=scaleValue, Path=Value}" />
</Canvas.LayoutTransform>
<Rectangle Name="rect" Width="40" Height="40" Canvas.Left="120" Canvas.Top="70" Fill="Blue" />
</Canvas>
</ScrollViewer>
</DockPanel>
</GroupBox>
</DockPanel>
</Grid>
Code Behind (VB.net)
Class MainWindow
''' Calculates the horizontal and vertical scrollbar offsets so that
''' the blue rectangle is centered within the scroll viewer.
Private Sub RecalculateCenter()
' the scale we are using
Dim scale As Double = scaleValue.Value
' get the rectangles current position within the canvas
Dim rectLeft As Double = Canvas.GetLeft(rect)
Dim rectTop As Double = Canvas.GetTop(rect)
' set our point of interest "Rect" equal to the the whole coordinates of the rectangle
Dim poi As Rect = New Rect(rectLeft, rectTop, rect.Width, rect.Height)
' get our view offset
Dim ofsViewWidth As Double = (scroll.ScrollableWidth - (((scroll.ViewportWidth / 2) - (rect.ActualWidth / 2)) * scale)) / scale
Dim ofsViewHeight As Double = (scroll.ScrollableHeight - (((scroll.ViewportHeight / 2) - (rect.ActualHeight / 2)) * scale)) / scale
' calculate our scroll bar offsets
Dim verticalOffset As Double = (poi.Top - ofsViewHeight) * scale
Dim horizontalOffset As Double = (poi.Left - ofsViewWidth) * scale
' record the output to the debug output window
Dim sb As New StringBuilder()
sb.AppendLine($"Scale : {scale}")
sb.AppendLine($"POI : {poi.ToString()}")
sb.AppendLine($"Rect : {rectLeft}x{rectTop}")
sb.AppendLine($"Extent : {scroll.ExtentWidth}x{scroll.ExtentHeight}")
sb.AppendLine($"Scrollable : {scroll.ScrollableWidth}x{scroll.ScrollableHeight}")
sb.AppendLine($"View Offset: {ofsViewWidth}x{ofsViewHeight}")
sb.AppendLine($"Horizontal : {horizontalOffset}")
sb.AppendLine($"Vertical : {verticalOffset}")
text.Text = sb.ToString()
' set the EditHorz and EditVert text box values, this will trigger the scroll
' bar offsets to fire via the TextChanged event handlers
EditHorz.Text = horizontalOffset.ToString()
EditVert.Text = verticalOffset.ToString()
End Sub
''' Try and parse the horizontal text box to a double, and set the scroll bar position accordingly
Private Sub SetScrollBarHorizontalOffset()
Dim ofs As Double = 0
If Double.TryParse(EditHorz.Text, ofs) Then
scroll.ScrollToHorizontalOffset(ofs)
End If
End Sub
''' Try and parse the vertical text box to a double, and set the scroll bar position accordingly
Private Sub SetScrollBarVerticalOffset()
Dim ofs As Double = 0
ofs = 0
If Double.TryParse(EditVert.Text, ofs) Then
scroll.ScrollToVerticalOffset(ofs)
End If
End Sub
''' Parse and set scrollbars positions for both Horizontal and Vertical
Private Sub SetScrollBarOffsets()
SetScrollBarHorizontalOffset()
SetScrollBarVerticalOffset()
End Sub
Private Sub ButtonCenter_Click(sender As Object, e As RoutedEventArgs)
RecalculateCenter()
End Sub
Private Sub ButtonReset_Click(sender As Object, e As RoutedEventArgs)
scroll.ScrollToVerticalOffset(0)
scroll.ScrollToHorizontalOffset(0)
End Sub
Private Sub EditHorz_TextChanged(sender As Object, e As TextChangedEventArgs)
SetScrollBarOffsets()
End Sub
Private Sub EditVert_TextChanged(sender As Object, e As TextChangedEventArgs)
SetScrollBarOffsets()
End Sub
End Class
After much more trial and error, and breaking it apart piece by piece, I was able to finally get this code to work. I hope someone else will find this useful. Solution is as follows:
XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Center and Zoom ScrollViewer Test" Height="600" Width="800" WindowStartupLocation="CenterScreen">
<Grid>
<DockPanel>
<GroupBox Header="Parameters" DockPanel.Dock="Top" Margin="10">
<StackPanel Orientation="Horizontal">
<GroupBox Header="Manually Set ScrollBar Positions" Margin="10">
<StackPanel Orientation="Horizontal">
<TextBox Name="EditHorz" Width="60" Margin="10" TextChanged="EditHorz_TextChanged" />
<Label Content="x" Margin="0 10 0 10" />
<TextBox Name="EditVert" Width="60" Margin="10" TextChanged="EditVert_TextChanged" />
</StackPanel>
</GroupBox>
<GroupBox Header="Scale" Margin="10">
<DockPanel>
<Label Content="{Binding ElementName=scaleValue, Path=Value, StringFormat={}{0:F2}}" DockPanel.Dock="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="40" />
<Slider Name="scaleValue" Minimum="1" Maximum="4" SmallChange="0.05" LargeChange="0.1" Width="200" VerticalAlignment="Center" />
</DockPanel>
</GroupBox>
</StackPanel>
</GroupBox>
<GroupBox Header="Debug Output" Margin="10">
<TextBox Name="text" FontFamily="Courier New" FontSize="12" DockPanel.Dock="Left" Width="500" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" Margin="10" />
</GroupBox>
<GroupBox Header="Proof" Margin="10">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Width="60" HorizontalAlignment="Left" Content="Center" Click="ButtonCenter_Click" Margin="10" />
<Button Width="60" HorizontalAlignment="Left" Content="Reset" Click="ButtonReset_Click" Margin="10" />
</StackPanel>
<ScrollViewer Name="scroll" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" Background="Green" Width="100" Height="100" VerticalAlignment="Top" Margin="10">
<Canvas Width="200" Height="200" Background="Red">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=scaleValue, Path=Value}" ScaleY="{Binding ElementName=scaleValue, Path=Value}" />
</Canvas.LayoutTransform>
<Rectangle Name="rect" Width="40" Height="40" Canvas.Left="120" Canvas.Top="70" Fill="Blue" />
</Canvas>
</ScrollViewer>
</DockPanel>
</GroupBox>
</DockPanel>
</Grid>
Code Behind (VB.net):
Class MainWindow
' Calculates the horizontal and vertical scrollbar offsets so that
' the blue rectangle is centered within the scroll viewer.
Private Sub RecalculateCenter()
' the scale we are using
Dim scale As Double = scaleValue.Value
' get our rectangles left and top properties
Dim rectLeft As Double = Canvas.GetLeft(rect) * scale
Dim rectTop As Double = Canvas.GetTop(rect) * scale
Dim rectWidth As Double = rect.Width * scale
Dim rectHeight As Double = rect.Height * scale
' set our point of interest "Rect" equal to the the whole coordinates of the rectangle
Dim poi As Rect = New Rect(rectLeft, rectTop, rectWidth, rectHeight)
' get top and left center values
Dim horizontalCenter As Double = ((scroll.ViewportWidth / 2) - (rectWidth / 2))
Dim verticalCenter As Double = ((scroll.ViewportHeight / 2) - (rectHeight / 2))
' get our center of viewport with relation to the poi
Dim viewportCenter As New Rect(horizontalCenter, verticalCenter, rectWidth, rectHeight)
' calculate our scroll bar offsets
Dim verticalOffset As Double = (poi.Top) - (viewportCenter.Top)
Dim horizontalOffset As Double = (poi.Left) - (viewportCenter.Left)
' record the output to the debug output window
Dim sb As New StringBuilder()
sb.AppendLine($"Scale .............. {scale,0:F2}")
sb.AppendLine($"rectLeft ........... {rectLeft,0:F0}")
sb.AppendLine($"rectTop ............ {rectTop,0:F0}")
sb.AppendLine($"POI ................ {poi.Left,0:F0},{poi.Top,0:F0},{poi.Width,0:F0},{poi.Height,0:F0}")
sb.AppendLine($"Horz Center ........ {horizontalCenter,0:F0}")
sb.AppendLine($"Vert Center ........ {verticalCenter,0:F0}")
sb.AppendLine($"View Center ........ {viewportCenter.Left,0:F0},{viewportCenter.Top,0:F0},{viewportCenter.Width,0:F0},{viewportCenter.Height,0:F0}")
sb.AppendLine($"Horizontal ......... {horizontalOffset,0:F0}")
sb.AppendLine($"Vertical ........... {verticalOffset,0:F0}")
sb.AppendLine($"------------------------------------")
sb.AppendLine($"ViewPort ........... {scroll.ViewportWidth,0:F0} x {scroll.ViewportHeight,0:F0}")
sb.AppendLine($"Extent ............. {scroll.ExtentWidth,0:F0} x {scroll.ExtentHeight,0:F0}")
sb.AppendLine($"Scrollable ......... {scroll.ScrollableWidth,0:F0} x {scroll.ScrollableHeight,0:F0}")
text.Text = sb.ToString()
' set the EditHorz and EditVert text box values, this will trigger the scroll
' bar offsets to fire via the TextChanged event handlers
EditHorz.Text = $"{horizontalOffset,0:F2}"
EditVert.Text = $"{verticalOffset,0:F2}"
End Sub
' Try and parse the horizontal text box to a double, and set the scroll bar position accordingly
Private Sub SetScrollBarHorizontalOffset()
Dim ofs As Double = 0
If Double.TryParse(EditHorz.Text, ofs) Then
scroll.ScrollToHorizontalOffset(ofs)
Else
scroll.ScrollToHome()
End If
End Sub
' Try and parse the vertical text box to a double, and set the scroll bar position accordingly
Private Sub SetScrollBarVerticalOffset()
Dim ofs As Double = 0
ofs = 0
If Double.TryParse(EditVert.Text, ofs) Then
scroll.ScrollToVerticalOffset(ofs)
Else
scroll.ScrollToHome()
End If
End Sub
' Parse and set scrollbars positions for both Horizontal and Vertical
Private Sub SetScrollBarOffsets()
SetScrollBarHorizontalOffset()
SetScrollBarVerticalOffset()
End Sub
Private Sub ButtonCenter_Click(sender As Object, e As RoutedEventArgs)
RecalculateCenter()
End Sub
Private Sub ButtonReset_Click(sender As Object, e As RoutedEventArgs)
EditHorz.Text = String.Empty
EditVert.Text = String.Empty
End Sub
Private Sub EditHorz_TextChanged(sender As Object, e As TextChangedEventArgs)
SetScrollBarOffsets()
End Sub
Private Sub EditVert_TextChanged(sender As Object, e As TextChangedEventArgs)
SetScrollBarOffsets()
End Sub
Private Sub scaleValue_ValueChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Double)) Handles scaleValue.ValueChanged
Dispatcher.BeginInvoke(Sub() RecalculateCenter())
End Sub
End Class

Read from DataGrid not working

I have created a simple datagrid to show some values, let the user change them and read back the changed values in the background program. Here is the XAML design file
<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>
<Button x:Name="ButAdd" Content="Add Row" HorizontalAlignment="Left" Height="23" Margin="362,34,0,0" VerticalAlignment="Top" Width="77"/>
<TextBox x:Name="TeBoResult" HorizontalAlignment="Left" Height="90" Margin="52,220,0,0" TextWrapping="Wrap" Text="Display the Row 0 colmn 1 changed value here:" VerticalAlignment="Top" Width="322" AcceptsReturn="True" IsManipulationEnabled="True"/>
<Button x:Name="ButRead" Content="Read Row 2" HorizontalAlignment="Left" Height="24" Margin="425,184,0,0" VerticalAlignment="Top" Width="82"/>
<DataGrid x:Name="DaGr" ItemsSource="{Binding}" HorizontalAlignment="Left" Height="133" Margin="10,75,0,0" VerticalAlignment="Top" Width="174" AutoGenerateColumns="False" IsManipulationEnabled="True" EnableColumnVirtualization="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Text" Binding="{Binding Path=No, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridCheckBoxColumn Header="Check box" Binding="{Binding Path=Sel}"/>
<DataGridComboBoxColumn Header="Combo box" Binding.XmlNamespaceManager="{Binding Path=Drop}"/>
</DataGrid.Columns>
</DataGrid>
<ComboBox x:Name="CoBo_IN" HorizontalAlignment="Left" Height="20" Margin="344,100,0,0" VerticalAlignment="Top" Width="84" Visibility="Visible">
<ComboBoxItem Content="Move" HorizontalAlignment="Left" Width="88"/>
<ComboBoxItem Content="Dock" HorizontalAlignment="Left" Width="88" Selected="ComboBoxItem_Selected"/>
</ComboBox>
</Grid>
And the backgroud vb.net code is here
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Imports DataGrid.datset
Imports System.Windows
Class MainWindow
Public Property coll As New ObservableCollection(Of bind)()
Private Sub ButAdd_Click(sender As Object, e As RoutedEventArgs) Handles ButAdd.Click
Dim qw As New bind()
qw.No = "Change Me"
qw.Sel = Nothing
qw.Drop = CoBo_IN
coll.Add(qw)
DaGr.ItemsSource = coll
End Sub
Private Sub ButRead_Click(sender As Object, e As RoutedEventArgs) Handles ButRead.Click
Dim val As String
For Each item As bind In DaGr.Items
val = item.No
TeBoResult.Text = TeBoResult.Text & val
Next
End Sub
End Class
Public Class datset : Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Structure bind
Public Property No As String
Public Property Sel As Boolean
Public Property Drop As ComboBox
End Structure
End Class
So When I click on the Add Row button the row with the default contents gets added in the TextColumn and CheckBoxColumn but the ComboBoxColumn dosent display the combo!!(but when I double click inside this cell the ComboBox appears but It is empty). What could be reason for this behaviour?
Next the user will change the contents inside the TextColumn and it gets changed in the GUI as required.
Next when the user clicks on the botton Read Row, all the contents of the TextColumn are read one after the other and is displayed in the Result text box. The problem is though the GUI has a new text when it is read sequentially the val variable still shows the previously bound values only. I thought the problem is with TwoWay binding but it seems to be something else.
Why does the read on DataGrid dosen't give an updated value?
I doesn't know to much of VB, but I can see some things here are not ok. For instance ItemsSource="{Binding}". Here you must to reference the collection to bind, in this case coll. The code should be something like this:
<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"
x:Name="window">
<Grid>
<Button x:Name="ButAdd" Content="Add Row" HorizontalAlignment="Left" Height="23" Margin="362,34,0,0" VerticalAlignment="Top" Width="77"/>
<TextBox x:Name="TeBoResult" HorizontalAlignment="Left" Height="90" Margin="52,220,0,0" TextWrapping="Wrap" Text="Display the Row 0 colmn 1 changed value here:" VerticalAlignment="Top" Width="322" AcceptsReturn="True" IsManipulationEnabled="True"/>
<Button x:Name="ButRead" Content="Read Row 2" HorizontalAlignment="Left" Height="24" Margin="425,184,0,0" VerticalAlignment="Top" Width="82"/>
<DataGrid x:Name="DaGr" ItemsSource="{Binding col, ElementName =window}" HorizontalAlignment="Left" Height="133" Margin="10,75,0,0" VerticalAlignment="Top" Width="174" AutoGenerateColumns="False" IsManipulationEnabled="True" EnableColumnVirtualization="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Text" Binding="{Binding Path=No, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridCheckBoxColumn Header="Check box" Binding="{Binding Path=Sel, Mode=TwoWay}"/>
<DataGridComboBoxColumn Header="Combo box" Binding.XmlNamespaceManager="{Binding Path=Drop, Mode=TwoWay"/>
</DataGrid.Columns>
</DataGrid>
<ComboBox x:Name="CoBo_IN" HorizontalAlignment="Left" Height="20" Margin="344,100,0,0" VerticalAlignment="Top" Width="84" Visibility="Visible">
<ComboBoxItem Content="Move" HorizontalAlignment="Left" Width="88"/>
<ComboBoxItem Content="Dock" HorizontalAlignment="Left" Width="88" Selected="ComboBoxItem_Selected"/>
</ComboBox>
</Grid>
Note the items source binding, and the name of the window, also note the bindings should be two way, for update the source too. This new binding references to the col collection. Now in he code behind you shoul not re-set the grid intems source each time you add an item. If the collection is an observable collection then it is added automatically, and now the items should works::
Private Sub ButAdd_Click(sender As Object, e As RoutedEventArgs) Handles ButAdd.Click
Dim qw As New bind()
qw.No = "Change Me"
qw.Sel = Nothing
qw.Drop = CoBo_IN
coll.Add(qw)
End Sub

WPF Image only updates in run-time on last frame

I'm new to XAML. I wrote following routine to rotate an image by a given angle (0 to 360). I put in a slider control to set the angle based on slider value. Works great! However, when running the program and clicking the 'spin' button,a For/Next loop goes from 0 to 360 and the image will only display the last angle rotation (360). I did put in a Sleep command to slow, just in case I wasn't catching the previous updates. Any help with why it won't update continuously would be greatly appreciated. Thank you.
Imports System.Threading.Thread
Imports System.Windows.Media.Imaging.BitmapImage
Class MainWindow
Private Sub Slider1_ValueChanged(sender As System.Object, e As System.Windows.RoutedPropertyChangedEventArgs(Of System.Double)) Handles Slider1.ValueChanged
' ---- when I adjust manually, this works perfectly
Dim rotateTransform1 As New RotateTransform
rotateTransform1.Angle = Slider1.Value
lblAngle.Content = rotateTransform1.Angle
Image1.RenderTransform = rotateTransform1
End Sub
Private Sub btnSpin_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnSpin.Click
Dim spinAngle as Double
For SpinAngle 0 to 360
spinWheel(spinAngle)
Sleep(50)
Next spinAngle
End Sub
Private Sub spinWheel(ByVal spinAngle)
Dim rotateTransform1 As New RotateTransform
rotateTransform1.Angle = SpinAngle 'Slider1.Value
Image1.RenderTransform = rotateTransform1
lblAngle.Content = rotateTransform1.Angle
Image1.InvalidateVisual()
End Sub
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
' Image1.createOption = BitmapCreateOptions.IgnoreImageCache
End Sub
End Class
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="677" Width="910">
<Grid Background="#FF006903">
<Grid.RowDefinitions>
<RowDefinition Height="238*" />
<RowDefinition Height="178*" />
</Grid.RowDefinitions>
<Button Content="SPIN!" Height="23" HorizontalAlignment="Left" Margin="521,101,0,0" Name="btnSpin" VerticalAlignment="Top" Width="75" Grid.Row="1" FontFamily="Tahoma" FontSize="15" FontWeight="ExtraBold" />
<Image Height="615" Margin="32,7,0,0" Name="Image1" Stretch="None" VerticalAlignment="Top"
RenderTransformOrigin=" 0.5,0.5" Source="/rotatePicture;component/Images/purp_wheel_cropped.png"
Grid.RowSpan="2" HorizontalAlignment="Left" Width="619" />
<Slider Height="25" HorizontalAlignment="Left" Margin="127,188,0,0" Name="Slider1" VerticalAlignment="Top" Width="350" Maximum="360" Grid.Row="1" />
<Label Content="Label" Height="28" HorizontalAlignment="Left" Margin="521,175,0,0" Name="lblAngle" VerticalAlignment="Top" Width="75" Grid.Row="1" FontFamily="Tahoma" FontSize="15" FontWeight="ExtraBold" />
<Image Height="29" HorizontalAlignment="Left" Margin="535,252,0,0" Name="Image2" Stretch="Fill" VerticalAlignment="Top" Width="55" Source="/rotatePicture;component/Images/wheel_pointer.png" />
</Grid>
</Window>
The problem with your approach is that you are blocking the UI thread by repeatedly calling Sleep in a Click handler. WPF provides a very elegant mechanism for what you are trying to do. It's called Animation.
A method that animates the rotation of a FrameworkElement may look like shown below in C# (sorry, but I don't speak VB).
private void RotateElement(
FrameworkElement element, double from, double to, TimeSpan duration)
{
var transform = element.RenderTransform as RotateTransform;
if (transform != null)
{
var animation = new DoubleAnimation(from, to, duration);
transform.BeginAnimation(RotateTransform.AngleProperty, animation);
}
}
Note that the RotateTransform must already be contained in the RenderTransform property of the FrameworkElement. It could for example be assigned in XAML like this:
<Image RenderTransformOrigin="0.5,0.5" ...>
<Image.RenderTransform>
<RotateTransform/>
</Image.RenderTransform>
</Image>
You would call the RotateElement method in a Button Click handler like this:
private void btnSpin_Click(object sender, RoutedEventArgs e)
{
RotateElement(Image1, 0, 360, TimeSpan.FromMilliseconds(360 * 50));
}
Note also that in your Slider1_ValueChanged it is also not necessary to create a new RotateTransform every time.
Moreover, there is rarely any need to call InvalidateVisual, as you do in spinWheel.
Clemens answer is good. Do not forget that you can pause/resume animations but only if you create them in code-behind. If I remember correctly you can't control animations if you start them in XAML.

Resources