Programmatically bind an image's canvas point in WPF - wpf

In my program I am dynamically creating images to be added into a canvas in a WPF window.
My question is: How can I bind the canvas.left and canvas.right point to a class property.
If the image existed before run-time I would make and bind it like this in XAML/WPF:
<Image Height="26" HorizontalAlignment="Left" Canvas.Left="{Binding left}" Canvas.Top="{Binding top}" Name="Image1" Stretch="Fill" VerticalAlignment="Top" Width="28" Source="/imageTest;component/Images/blue-pin-md.png" />
What I have in VB.net:
'Create array of images
Dim myImages(5) as myImage
For i = 0 to myImages.count - 1
myImages(i) = New myImage
'set datacontext if I can bind
myImages(i).image.DataContext = myImages(i)
canvas1.Children.Add(myImages(i).image)
Next
myImage class:
Imports System.ComponentModel
Public Class myImage
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Private Property _image as New Image
Private Property _left As Double
Private Property _top As Double
Private Shared Property r As New Random()
Public Sub New()
_image.Width = 28
_image.Height = 26
_image.Source = New System.Windows.Media.Imaging.BitmapImage(New Uri("/imageTest;component/Images/blue-pin-md.png", UriKind.Relative))
'the below works without binding if I just want to set and leave them in one place but I would like to bind them so that I can move them relative to other data
'_left = r.Next(0, System.Windows.SystemParameters.PrimaryScreenWidth)
'_top = r.Next(0, System.Windows.SystemParameters.PrimaryScreenHeight)
End Sub
Public ReadOnly Property image As Image
Get
Return _image
End Get
End Property
Public ReadOnly Property left As Double
Get
Return _left
End Get
End Property
Public ReadOnly Property top As Double
Get
Return _top
End Get
End Property
'a possible move method that would take advantage of the binding
Public Sub move()
_top += 1
_left += 1
NotifyPropertyChanged("left")
NotifyPropertyChanged("top")
End Sub

Not sure how to write it in VB, but in C# it would look like this:
var leftBinding = new Binding
{
Path = new PropertyPath("left"),
Source = myImages[i]
};
var topBinding = new Binding
{
Path = new PropertyPath("top"),
Source = myImages[i]
};
myImages[i].image.SetBinding(Canvas.LeftProperty, leftBinding);
myImages[i].image.SetBinding(Canvas.TopProperty, topBinding);
Or perhaps simpler, with DataContext:
myImages[i].image.DataContext = myImages[i];
myImages[i].image.SetBinding(Canvas.LeftProperty, "left");
myImages[i].image.SetBinding(Canvas.TopProperty, "top");

Thanks to Clemens C# code I was able to get it working. Below is the code that I used. The .SetBindings(,) was the key for me.
For i = 0 To myImages.Count - 1
myImages(i) = New myImage
myImages(i).image.DataContext = myImages(i)
myImages(i).image.SetBinding(Canvas.LeftProperty, "left")
myImages(i).image.SetBinding(Canvas.TopProperty, "top")
canvas1.Children.Add(myImages(i).image)
Next

Related

Wpf DataTrigger not firing on property change [duplicate]

This question already has answers here:
variable/property changed event in vb.net
(4 answers)
Closed 5 years ago.
I'm trying to set up a listview to show the selected Items in blue, based on a binding, but the datatrigger won't fire. If I set the trigger value to 0 (initial value), the listviewitems create with the blue background, but won't change afterward.
VB code:
Private Sub SetGridViewDynamically()
Dim myGridView As New GridView
myGridView.AllowsColumnReorder = True
Dim gvc1 As New GridViewColumn
gvc1.DisplayMemberBinding = New Binding("A")
gvc1.Header = "1"
gvc1.Width = TestListView.ActualWidth * 0.19
myGridView.Columns.Add(gvc1)
Dim gvc2 As New GridViewColumn
gvc2.DisplayMemberBinding = New Binding("B")
gvc2.Header = "2"
gvc2.Width = TestListView.ActualWidth * 0.39
myGridView.Columns.Add(gvc2)
Dim gvc3 As New GridViewColumn()
gvc3.DisplayMemberBinding = New Binding("C")
gvc3.Header = "3"
gvc3.Width = TestListView.ActualWidth * 0.19
myGridView.Columns.Add(gvc3)
Dim gvc4 As New GridViewColumn()
gvc4.DisplayMemberBinding = New Binding("D")
gvc4.Header = "4"
gvc4.Width = TestListView.ActualWidth * 0.19
myGridView.Columns.Add(gvc4)
TestListView.View = myGridView
Dim style As New Style
style.TargetType = GetType(ListViewItem)
style.Setters.Add(New Setter(ListViewItem.HorizontalContentAlignmentProperty, HorizontalAlignment.Center))
Dim x = New DataTrigger
x.Binding = New Binding("rowselected")
x.Value = 0
x.Setters.Add(New Setter(TestListView.BackgroundProperty, DarkGradientSample.Background))
Dim x2 = New Trigger
x2.Property = ItemsControl.AlternationIndexProperty
x2.Value = 1
x2.Setters.Add(New Setter(TestListView.BackgroundProperty, LightGradientSample.Background))
Dim x3 As New DataTrigger
x3.Binding = New Binding("rowselected")
x3.Value = 1
x3.Setters.Add(New Setter(TestListView.BackgroundProperty, BlueGradientSample.Background))
style.Triggers.Add(x)
style.Triggers.Add(x2)
style.Triggers.Add(x3)
TestListView.ItemContainerStyle = style
End Sub
and the XAML for the listview in question:
<ListView x:Name="TestListView" Grid.Column="1" Margin="10" Grid.Row="2" ItemsSource="{Binding picks}" FontSize="48" AlternationCount="2" Foreground="White" HorizontalContentAlignment="Stretch">
</ListView>
Edit: Got it. I wasn't implementing Inotifypropertychanged properly. here's the interface properly implemented in the class
Public Class PickLocation
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _rowselected As Integer
Public Property rowselected As Integer
Get
Return _rowselected
End Get
Set(value As Integer)
_rowselected = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("rowselected"))
End Set
End Property
Your class should implement the INotifyPropertyChanged and raise the PropertyChanged event whenever the property is set to a new value:
Public Class PickLocation
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
Private _rowselected As Integer
Public Property rowselected() As Integer
Get
Return _rowselected
End Get
Set(ByVal value As Integer)
If Not(value = _rowselected) Then
_rowselected = value
NotifyPropertyChanged()
End If
End Set
End Property
End Class

ItemsControl bound to ObservableColellection not updating UI on property change

After a lot of headache and late hours, I've given up on trying to solve the problem to this answer myself. While there is a lot of literature for very similar issues that can be found, I haven't been able to find an exact solution to my particular problem.
I am having trouble getting my ItemsControl using a canvas as the ItemsPanel to update the UI after a property of an item within its ItemsSource has been modified.
I have created a very clean sample application to demonstrate exactly what's going on.
In my sample application, I have a view 'MainWindow.xaml', a viewmodel 'MainWindowViewModel.vb' which is inheriting 'ViewModelBase.vb', and lastly a command delegate 'DelegateCommand.vb' which is used to create RelayCommands to update the ItemSource of my ItemsControl.
First, MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApp"
x:Class="MainWindow" Title="MainWindow" Height="347" Width="525" Background="Black">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<!-- LINE SEGMENTS -->
<ItemsControl x:Name="ic1" ItemsSource="{Binding LineData, Mode=OneWay, NotifyOnTargetUpdated=True}" HorizontalAlignment="Left" Height="246" VerticalAlignment="Top" Width="517" Background="#FF191919" BorderBrush="#FF444444">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="White" StrokeThickness="6"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Refresh Canvas" HorizontalAlignment="Left" Margin="350,261,0,0" VerticalAlignment="Top" Width="124" Height="40" FontFamily="Verdana" FontWeight="Bold" Click="Button_Click"/>
<Button Content="Command 1" Command="{Binding Command1}" HorizontalAlignment="Left" Margin="45,261,0,0" VerticalAlignment="Top" Width="124" Height="40" FontFamily="Verdana" FontWeight="Bold"/>
<Button Content="Command 2" Command="{Binding Command2}" HorizontalAlignment="Left" Margin="198,261,0,0" VerticalAlignment="Top" Width="124" Height="40" FontWeight="Bold" FontFamily="Verdana"/>
</Grid>
</Window>
As you can see, the DataContext of my Window is MainWindowViewModel, and the binding of the ItemSource is LineData (located within that VM).
In addition, I have three buttons. The first two buttons execute ICommands, while the third button executes a behind-code refresh of the ItemsControl (This is for debugging purposes, to prove that a bound property within the ItemSource is being updated while the UI is not). More on that later.
The first button is bound to Command1 in the VM, while the second button is bound to Command2 in the VM.
Next, MainWindowViewModel.vb:
Imports System.Collections.ObjectModel
Public Class MainWindowViewModel
Inherits ViewModelBase
' Sample line data variable
Private _LineData As ObservableCollection(Of LineStructure) = GetLineData()
Public Property LineData As ObservableCollection(Of LineStructure)
Get
Return _LineData
End Get
Set(value As ObservableCollection(Of LineStructure))
_LineData = value
OnPropertyChanged("LineData")
End Set
End Property
' ICommands
Private _Command1 As ICommand
Public ReadOnly Property Command1 As ICommand
Get
If _Command1 Is Nothing Then
_Command1 = New MVVM.RelayCommand(AddressOf ExecuteCommand1)
End If
Return _Command1
End Get
End Property
Private _Command2 As ICommand
Public ReadOnly Property Command2 As ICommand
Get
If _Command2 Is Nothing Then
_Command2 = New MVVM.RelayCommand(AddressOf ExecuteCommand2)
End If
Return _Command2
End Get
End Property
' ICommand Methods
Private Sub ExecuteCommand1()
' Re-arrange LineData(0) to make a plus sign on the canvas
' This works - Assigning a new value to an item of the collection updates the canvas
LineData(0) = New LineStructure With {.X1 = "175", .Y1 = "50", .X2 = "175", .Y2 = "150"}
End Sub
Private Sub ExecuteCommand2()
' Put LineData(0) back into its original position
' This doesn't work - Modifying the PROPERTY of an item in the collection does not update the canvas.. even with INotifyPropertyChange being called
LineData(0).X1 = "50"
LineData(0).Y1 = "50"
LineData(0).X2 = "300"
LineData(0).Y2 = "50"
OnPropertyChanged("LineData")
End Sub
' Misc methods
Private Function GetLineData() As ObservableCollection(Of LineStructure)
Dim tmpList As New ObservableCollection(Of LineStructure)
' Create two horizontal parallel lines
tmpList.Add(New LineStructure With {.X1 = "50", .Y1 = "50", .X2 = "300", .Y2 = "50"})
tmpList.Add(New LineStructure With {.X1 = "50", .Y1 = "100", .X2 = "300", .Y2 = "100"})
Return tmpList
End Function
End Class
Public Class LineStructure
Public Property X1
Public Property Y1
Public Property X2
Public Property Y2
End Class
In my viewmodel, I have defined LineData immediately (this is what my ItemsSource is bound to), so we have some data for our ItemSource ready to be displayed in the canvas upon execution. It is defined by a GetLineData() function, which simply returns a populated ObservableCollection of 2 lines.
When the application first starts, there are two horizontal, parallel lines displayed.
The LineData variable is an ObservableObject of a LineStructure class that I have defined, which simply contains X1, Y1, X2, Y2 strings for the respective objects to bind to and display within the canvas.
Command1 (again, this is bound to the first button) assigns a new LineStructure to the first index of LineData. When this is executed, everything works fantastic; the UI updates as expected and everyone is happy. This makes the lines appear as a plus sign on the canvas.
Here's where the problem begins:
Command2 is not going to assign a new LineStructure to the first LineData index like Command1 does, instead it's going to re-define the properties within the first LineData index individually. If this were to work, it would re-arrange the first line, and both lines on the canvas would be horizontally parallel again.
This however does not update the canvas/UI - and I can't figure out why. I have read numerous articles and tried many different solutions to no avail.
If anyone can explain why the binding does not update upon modifying a property rather than re-declaring the LineStructure index all together, please let me know, I would greatly appreciate it.
One final thing to note, I have managed to find a solution which will get what I need done, however I don't believe I should have to use it.. I would think the bindings should be able to take care of detecting any property changes.
For anyone interested, see the following snippet for a makeshift solution to update the canvas on a property change.
I have added NotifyOnTargetUpdated=True and TargetUpdated="RefreshCanvas" to my ItemsControl declaration in xaml.
What this does is calls a method named RefreshCanvas(), which executes ic1.Items.Refresh() from the MainWindow's code-behind (you can find the code-behind at the end of this post). This refreshes the ItemsControl items, and thus the canvas is refreshed and displays updates to the bound collection.
<ItemsControl x:Name="ic1" TargetUpdated="RefreshCanvas" ItemsSource="{Binding LineData, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True}" HorizontalAlignment="Left" Height="246" VerticalAlignment="Top" Width="517" Background="#FF191919" BorderBrush="#FF444444">
I will include my other files just for reference, since it might be relevant:
ViewModelBase.vb:
Imports System.ComponentModel
Public MustInherit Class ViewModelBase
Implements INotifyPropertyChanged, IDisposable
#Region "Constructor"
Protected Sub New()
End Sub
#End Region ' Constructor
#Region "DisplayName"
' Returns the user-friendly name of this object.
' Child classes can set this property to a new value, or override it to determine the value on-demand.
Private privateDisplayName As String
Public Overridable Property DisplayName() As String
Get
Return privateDisplayName
End Get
Protected Set(ByVal value As String)
privateDisplayName = value
End Set
End Property
#End Region ' DisplayName
#Region "Debugging Aids"
' Warns the developer if this object does not have a public property with the specified name.
' This method does not exist in a Release build.
<Conditional("DEBUG"), DebuggerStepThrough()> _
Public Sub VerifyPropertyName(ByVal propertyName As String)
' Verify that the property name matches a real, public, instance property on this object.
If TypeDescriptor.GetProperties(Me)(propertyName) Is Nothing Then
Dim msg As String = "Invalid property name: " & propertyName
If Me.ThrowOnInvalidPropertyName Then
Throw New Exception(msg)
Else
Debug.Fail(msg)
End If
End If
End Sub
' Returns whether an exception is thrown, or if a Debug.Fail() is used when an invalid property name is passed to the VerifyPropertyName method.
' The default value is false, but subclasses used by unit tests might override this property's getter to return true.
Private privateThrowOnInvalidPropertyName As Boolean
Protected Overridable Property ThrowOnInvalidPropertyName() As Boolean
Get
Return privateThrowOnInvalidPropertyName
End Get
Set(ByVal value As Boolean)
privateThrowOnInvalidPropertyName = value
End Set
End Property
#End Region ' Debugging Aides
#Region "INotifyPropertyChanged Members"
' Raised when a property on this object has a new value.
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
' Raises this object's PropertyChanged event.
' <param name="propertyName">The property that has a new value.</param>
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
Me.VerifyPropertyName(propertyName)
Dim handler As PropertyChangedEventHandler = Me.PropertyChangedEvent
If handler IsNot Nothing Then
Dim e = New PropertyChangedEventArgs(propertyName)
handler(Me, e)
End If
End Sub
#End Region ' INotifyPropertyChanged Members
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' Invoked when this object is being removed from the application and will be subject to garbage collection.
Public Sub Dispose() Implements IDisposable.Dispose
Me.OnDispose()
End Sub
' Child classes can override this method to perform clean-up logic, such as removing event handlers.
Protected Overridable Sub OnDispose()
End Sub
' Controla el tancament del ViewModel.
' <returns></returns>
' <remarks></remarks>
Public Overridable Function CanClose() As Boolean
Return Nothing
End Function
#If DEBUG Then
' Useful for ensuring that ViewModel objects are properly garbage collected.
Protected Overrides Sub Finalize()
Dim msg As String = String.Format("{0} ({1}) ({2}) Finalized", Me.GetType().Name, Me.DisplayName, Me.GetHashCode())
System.Diagnostics.Debug.WriteLine(msg)
End Sub
#End If
#End Region
End Class
DelegateCommand.vb:
Imports System.Windows.Input
Namespace MVVM
Public NotInheritable Class RelayCommand
Implements ICommand
#Region " Declarations "
Private ReadOnly _objCanExecuteMethod As Predicate(Of Object) = Nothing
Private ReadOnly _objExecuteMethod As Action(Of Object) = Nothing
#End Region
#Region " Events "
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
If _objCanExecuteMethod IsNot Nothing Then
CommandManager.InvalidateRequerySuggested()
End If
End RaiseEvent
End Event
#End Region
#Region " Constructor "
Public Sub New(ByVal objExecuteMethod As Action(Of Object))
Me.New(objExecuteMethod, Nothing)
End Sub
Public Sub New(ByVal objExecuteMethod As Action(Of Object), ByVal objCanExecuteMethod As Predicate(Of Object))
If objExecuteMethod Is Nothing Then
Throw New ArgumentNullException("objExecuteMethod", "Delegate comamnds can not be null")
End If
_objExecuteMethod = objExecuteMethod
_objCanExecuteMethod = objCanExecuteMethod
End Sub
#End Region
#Region " Methods "
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
If _objCanExecuteMethod Is Nothing Then
Return True
Else
Return _objCanExecuteMethod(parameter)
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
If _objExecuteMethod Is Nothing Then
Return
Else
_objExecuteMethod(parameter)
End If
End Sub
#End Region
End Class
End Namespace
Namespace MVVM
Public NotInheritable Class RelayCommand(Of T)
Implements ICommand
#Region " Declarations "
Private ReadOnly _objCanExecuteMethod As Predicate(Of T) = Nothing
Private ReadOnly _objExecuteMethod As Action(Of T) = Nothing
#End Region
#Region " Events "
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _objCanExecuteMethod IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
If _objCanExecuteMethod IsNot Nothing Then
CommandManager.InvalidateRequerySuggested()
End If
End RaiseEvent
End Event
#End Region
#Region " Constructors "
Public Sub New(ByVal objExecuteMethod As Action(Of T))
Me.New(objExecuteMethod, Nothing)
End Sub
Public Sub New(ByVal objExecuteMethod As Action(Of T), ByVal objCanExecuteMethod As Predicate(Of T))
If objExecuteMethod Is Nothing Then
Throw New ArgumentNullException("objExecuteMethod", "Delegate comamnds can not be null")
End If
_objExecuteMethod = objExecuteMethod
_objCanExecuteMethod = objCanExecuteMethod
End Sub
#End Region
#Region " Methods "
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
If _objCanExecuteMethod Is Nothing Then
Return True
Else
Return _objCanExecuteMethod(DirectCast(parameter, T))
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_objExecuteMethod(DirectCast(parameter, T))
End Sub
#End Region
End Class
End Namespace
MainWindow.xaml.vb (the code-behind of MainWindow):
Class MainWindow
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
ic1.Items.Refresh()
End Sub
Private Sub RefreshCanvas(sender As Object, e As DataTransferEventArgs)
sender.Items.Refresh()
End Sub
End Class
Thank you for any help that might be offered to point me in the right direction, and hopefully this can help someone else out as well.
***** UPDATE, ISSUE SOLVED *****
E-Bat has so kindly pointed out that the properties of the LineData structure themselves need to implement INotifyPropertyChanged. I have implemented this change and added the updated and working 'MainWindowViewModel.xaml' code below:
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class MainWindowViewModel
Inherits ViewModelBase
' Sample line data variable
Private _LineData As ObservableCollection(Of LineData) = GetLineData()
Public Property LineData As ObservableCollection(Of LineData)
Get
Return _LineData
End Get
Set(value As ObservableCollection(Of LineData))
_LineData = value
OnPropertyChanged("LineData")
End Set
End Property
' ICommands
Private _Command1 As ICommand
Public ReadOnly Property Command1 As ICommand
Get
If _Command1 Is Nothing Then
_Command1 = New MVVM.RelayCommand(AddressOf ExecuteCommand1)
End If
Return _Command1
End Get
End Property
Private _Command2 As ICommand
Public ReadOnly Property Command2 As ICommand
Get
If _Command2 Is Nothing Then
_Command2 = New MVVM.RelayCommand(AddressOf ExecuteCommand2)
End If
Return _Command2
End Get
End Property
' ICommand Methods
Private Sub ExecuteCommand1()
' Re-arrange LineData(0) to make a plus sign on the canvas
' This works - Assigning a new value to an item of the collection updates the canvas
LineData(0) = New LineData With {.X1 = "175", .Y1 = "50", .X2 = "175", .Y2 = "150"}
End Sub
Private Sub ExecuteCommand2()
' Put LineData(0) back into its original position
' Now it works, it's voodoo!
LineData(0).X1 = "50"
LineData(0).Y1 = "50"
LineData(0).X2 = "300"
LineData(0).Y2 = "50"
End Sub
' Misc methods
Private Function GetLineData() As ObservableCollection(Of LineData)
Dim tmpList As New ObservableCollection(Of LineData)
' Create two horizontal parallel lines
tmpList.Add(New LineData With {.X1 = "50", .Y1 = "50", .X2 = "300", .Y2 = "50"})
tmpList.Add(New LineData With {.X1 = "50", .Y1 = "100", .X2 = "300", .Y2 = "100"})
OnPropertyChanged("LineData")
Return tmpList
End Function
End Class
Public Class LineData
Implements INotifyPropertyChanged
Private _X1 As String
Public Property X1 As String
Get
Return _X1
End Get
Set(value As String)
_X1 = value
OnPropertyChanged("X1")
End Set
End Property
Private _Y1 As String
Public Property Y1 As String
Get
Return _Y1
End Get
Set(value As String)
_Y1 = value
OnPropertyChanged("Y1")
End Set
End Property
Private _X2 As String
Public Property X2 As String
Get
Return _X2
End Get
Set(value As String)
_X2 = value
OnPropertyChanged("X2")
End Set
End Property
Private _Y2 As String
Public Property Y2 As String
Get
Return _Y2
End Get
Set(value As String)
_Y2 = value
OnPropertyChanged("Y2")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
When you replace an item from an ObservableCollection, the old reference will be removed first and then it adds the new one, so ObservableCollection will be rising its events and that is why the first commands works as magic.
Now for the second command to refresh the UI you have to make the items itself, LineStructure, implementers of INotifyPropertyChanged so any changes to its properties will be refreshed by the binding. So say goodbye to automated properties for this class.
Public Class LineStructure
Implements INotifyPropertyChanged
Private _x1 As String
Public Property X1 As String
Get
Return _x1
End Get
Set(value As String)
If _x1 = value Then Return
_x1 = value
OnPropertyChanged("X1")
End Set
End Property
End Class

Passing data through XAML into VB

ORIGINAL (Solved by Clemens):
I have code (below) where I am trying to pass a parameter through XAML into VB. This gives it an error code " 'tabText' property was already registered by 'customTabPanel' " and I am not sure what I should do here as this is my first attempt takling this problem
NEW:
This still doesn't pass the text through properly and I have no idea why. Any help will be appreciated.
VB:
Public Class customTabPanel
Inherits Grid
Dim workSpaceAssociation As Grid
Public ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("tabText", GetType(String), GetType(customTabPanel), New PropertyMetadata(String.Empty))
Public Property tabText() As String
Get
Return DirectCast(GetValue(TextProperty), String)
End Get
Set(value As String)
SetValue(TextProperty, value)
End Set
End Property
Sub New()
Me.Height = 20
Me.Background = New SolidColorBrush(Color.FromRgb(27, 27, 28))
Dim textBlock As New TextBlock
textBlock.Foreground = New SolidColorBrush(Colors.White)
textBlock.Text = tabText
textBlock.Width = 100
textBlock.Padding = New Thickness(10, 0, 25, 0)
Dim closeWorkspace As New TextBlock
closeWorkspace.HorizontalAlignment = Windows.HorizontalAlignment.Right
closeWorkspace.Text = ""
closeWorkspace.Foreground = New SolidColorBrush(Colors.White)
closeWorkspace.Height = 15
closeWorkspace.Width = 15
closeWorkspace.FontFamily = New FontFamily("Segoe UI Symbol")
'add'
Me.Children.Add(textBlock)
Me.Children.Add(closeWorkspace)
Me.Width = textBlock.Width
Me.Margin = New Thickness(5, 0, 0, 0)
Me.HorizontalAlignment = Windows.HorizontalAlignment.Left
End Sub
Sub SetText(ByVal t As String)
tabText = t
End Sub
Function GetText() As String
Return tabText
End Function
End Class
XAML:
<ThisIsAwsome:customTabPanel tabText="Start Screen" />
The dependency property field has to be declared as Shared:
Public Shared ReadOnly TextProperty As DependencyProperty = ...
In order to get notified about updated property values, you would also have to register a PropertyChangedCallback with the property's metadata:
Public Shared ReadOnly TextProperty As DependencyProperty =
DependencyProperty.Register(
"tabText", GetType(String), GetType(customTabPanel),
New PropertyMetadata(
String.Empty, New PropertyChangedCallback(AddressOf TabTextChanged)))
In the TabTextChanged callback you would set your TextBlock's Text property (no idea though if it's valid VB):
Private Shared Sub TabTextChanged(
ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim panel As customTabPanel = CType(d, customTabPanel)
panel.textBlock.Text = CType(e.NewValue, String)
End Sub
You'll find a detailed explanation in the Custom Dependency Properties article on MSDN.

Improve performance moving multiple wpf elements

Doing projects for fun helps me learn. Here is a project that I am working on and I have already learned so much. I would like to see the program be less CPU intensive though. Does anyone have any suggestions on how I could do that?
Basically this program just overlays some snow flakes onto a computer screen.
EDIT:
What I am currently taking a look into is to see if I can use DoubleAnimationUsingPath and bind to the PathGeometry. While I am trying to figure this out I welcome any suggestion or tips regarding this method or any other.
WPF/XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
AllowsTransparency="True"
WindowStyle="None"
Title="MainWindow" Height="350" Width="525" Background="Transparent" Topmost="True" WindowState="Maximized" ResizeMode="NoResize">
<Canvas Name="canvas1">
</Canvas>
</Window>
VB.NET Main Window:
Imports System.ComponentModel
Class MainWindow
Dim bw As New BackgroundWorker
Dim flakes(17) As flake
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
For i = 0 To flakes.Count - 1
flakes(i) = New flake
flakes(i).image.DataContext = flakes(i)
flakes(i).image.SetBinding(Canvas.LeftProperty, "left")
flakes(i).image.SetBinding(Canvas.TopProperty, "top")
canvas1.Children.Add(flakes(i).image)
Next
AddHandler bw.DoWork, AddressOf backgroundMover
bw.RunWorkerAsync()
End Sub
Private Sub backgroundMover()
While (True)
For Each f In flakes
f.move()
Next
System.Threading.Thread.Sleep(50)
End While
End Sub
End Class
VB.Net flake class:
Imports System.ComponentModel
Public Class flake
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Private Property startLeft As Double
Private Property _left As Double
Private Property _top As Double
Private Property speed As Double
Private Property amplitude As Double
Private Property period As Double
Public Property image As New Image
Private Shared Property r As New Random
Public Sub New()
_image.Width = 28
_image.Height = 26
_image.Source = New System.Windows.Media.Imaging.BitmapImage(New Uri("/snowTest;component/Images/blue-pin-md.png", UriKind.Relative))
startFresh()
End Sub
Public ReadOnly Property left As Double
Get
Return _left
End Get
End Property
Public ReadOnly Property top As Double
Get
Return _top
End Get
End Property
Public Sub startFresh()
_top = -30
amplitude = r.Next(5, 35)
period = 1 / r.Next(20, 60)
speed = r.Next(15, 25) / 10
startLeft = r.Next(0, System.Windows.SystemParameters.PrimaryScreenWidth)
End Sub
Public Sub move()
If _top > System.Windows.SystemParameters.PrimaryScreenHeight Then
startFresh()
Else
_top += speed
_left = amplitude * Math.Cos(period * _top) + startLeft
End If
NotifyPropertyChanged("top")
NotifyPropertyChanged("left")
End Sub
End Class
Updating LeftProperty and TopProperty on your UIElement inside the canvas forces a new layout pass for each update. Layout in WPF is pretty expensive from a performances point of view. You should use UIElement.RenderTransform Property instead.

RelayCommand commandParameter is null

I have a button on my usercontrol
<Button Grid.Row="13" Grid.Column="7" Content="Save" Name="btnSave"
CommandParameter="{Binding CompanyDetails}"
Command="{Binding SaveCommand}" />
SaveCommand is a RelayCommand declared in the ViewModel.
CompanyDetails is a property in the same ViewModel.
The mentioned above ViewModel is the datacontext of my UserControl.
Public Class CompanyViewModel
Inherits ViewModelBase
Implements IDataErrorInfo
Private _myData As DataFromDatabase
Private _companyDetails As New CompanyDetailsBase
Private _saveCommand As RelayCommandWithParameter
Public Property CompanyDetails() As CompanyDetailsBase
Get
Return _companyDetails
End Get
Set(ByVal value As CompanyDetailsBase)
_companyDetails = value
OnPropertyChanged("CompanyDetails")
End Set
End Property
Public Property SaveCommand() As RelayCommandWithParameter
Get
If _saveCommand Is Nothing Then
Dim saveAction As New Action(Of Object)(AddressOf Me.SaveIt)
_saveCommand = New RelayCommandWithParameter(saveAction) ', Me.CanSave)
End If
Return _saveCommand
End Get
Set(ByVal value As RelayCommandWithParameter)
_saveCommand = value
End Set
End Property
Public Sub New()
_myData = New DataFromDatabase(My.Settings.ConnectionString)
_saveCommand = New RelayCommandWithParameter(New Action(Of Object)(AddressOf SaveIt))
End Sub
Public Function GetCompanyDetails(ByVal sap As String) As CompanyDetailsBase
CompanyDetails = _myData.GetCompanyDetails(sap)
Return CompanyDetails
End Function
Public Sub SaveIt(ByVal c As CompanyDetailsBase)
MessageBox.Show("Save customer " & c.Sap)
End Sub
End Class
The other fields on the usercontrol are bound to an ObjectDataProvider, which calls the GetCompanyDetails function. The CompanyDetails is set right each time this function is called, until I call SaveIt subroutine. When SaveIt is called its parameter is always Nothing. Where do I make the mistake?
Thank you a lot for answers.
In the constructer of CompanyViewModel populate the CompanyDetails with your GetCompanyDetails function. Change the constructor as bellow,
Public Sub New()
_myData = New DataFromDatabase(My.Settings.ConnectionString)
_saveCommand = New RelayCommandWithParameter(New Action(Of Object)(AddressOf SaveIt))
_companyDetails = GetCompanyDetails()
End Sub
This might help.
Good luck.
It's been a while since I did VB, and I never did it with WPF, but I think I know what your problem is.
The way you are initializing the Command, you aren't actually passing the parameter in.
When I do it in c# it looks like this =>
_saveCommand = new RelayCommandWithParameter(
(param) =>
{
SaveIt((int)param);
},
// this line is the same as your commented out Me.CanSave
(param) => { return this.CanDisplaySelectedPolicy; });
So you can see that in the lamda expression I pass a parameter called param, that is then put into the SaveIt method.
When you create your new action, try putting a lamda expression in it like the one above, so you can pass a parameter into the SaveIt method.

Resources