I am trying to get a label to show specific text while also being bound to a variable in the VB.Net code. I can make a binding but I cant get it to add the static text.
What I have so far:
<Label x:Name="TestLabel" Content="{Binding Path=Row, StringFormat='Row #{0}'}"
HorizontalAlignment="Left"
Height="35"
Margin="203,21,0,0"
VerticalAlignment="Top"
Width="83"
FontSize="18">
with
Public Class Row
Implements INotifyPropertyChanged
Private _Row As Byte
Public Property Row() As Byte
Get
Return _Row
End Get
Set(ByVal value As Byte)
_Row = value
OnPropertyChanged(New PropertyChangedEventArgs("Row"))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
If Not PropertyChangedEvent Is Nothing Then
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
End Class
and
Private Rows As New Row
Public Sub New()
InitializeComponent()
TestLabel.DataContext = Rows
Rows.Row = MyTextBox.Text.HandledStringtoSByte
End Sub
The extension code (since I have a custom extension):
''' <summary>
''' Handles conversion of string variable to Tiny Integer
''' </summary>
''' <param name="s"></param>
''' <param name="I">Returned if conversion fails.</param>
''' <returns>Signed 8bit Integer</returns>
''' <remarks></remarks>
<Runtime.CompilerServices.Extension()> _
Public Function HandledStringtoSByte(ByRef S As String, Optional I As SByte = 0) As SByte
Try
If S = String.Empty Then
Return I
Else
Return SByte.Parse(S)
End If
Catch
Dim result As String = String.Empty
Dim ReturnByte As SByte
Dim Parsed As Byte
For Each Character In S.ToCharArray
If Character = "-" Then
If S.Substring(0, 1).ToString <> "-" Then
Exit For
End If
End If
If Character = "." Then
Exit For
End If
If Byte.TryParse(Character, Parsed) Then
result = result + Parsed.ToString
End If
Next
If result <> String.Empty Then
If SByte.TryParse(result, ReturnByte) Then
Return SByte.Parse(ReturnByte)
Else
If Short.Parse(result) > Short.Parse(SByte.MaxValue.ToString) Then
Return SByte.MaxValue
ElseIf Short.Parse(result) < Short.Parse(SByte.MinValue.ToString) Then
Return SByte.MinValue
Else
Return SByte.Parse(ReturnByte)
End If
End If
Else
Return I
End If
End Try
End Function
Now I thought that using the stringformat in binding would add the static text and place the bound variable into the {0} spot but all is gives me is the bound variable in the label.
What am i doing wrong?
Binding target is Content property which is Object type, that is why you cannot use StringFormat with binding.
Instead use ContentStringFormat property
<Label Content="{Binding Path=Row}"
ContentStringFormat="Row #{0}" />
Another approach: create readonly property in the ViewModel which will represent value in wanted format
Private _Row As Byte
Public Property Row() As Byte
Get
Return _Row
End Get
Set(ByVal value As Byte)
_Row = value
OnPropertyChanged(New PropertyChangedEventArgs("Row"))
OnPropertyChanged(New PropertyChangedEventArgs("RowText"))
End Set
End Property
Public ReadOnly Property RowText As String
Get
Return String.Format("Row #{0}", Me.Row)
End Get
End Property
Then bind this property to the View
<Label Content="{Binding Path=RowText}"/>
The problem is that Binding.StringFormat is "a string that specifies how to format the binding if it displays the bound value as a string". In practice it seems to work only if the target property is of type string - as you pointed out it's working for TextBlock.Text (which is of type string) and not for Label.Content (which is of type object). There are several ways to approach this problem, one of them would be to nest a TextBlock in the Content property:
<Label>
<TextBlock Text="{Binding Path=Row, StringFormat='Row #{0}'}" />
</Label>
This doesn't really introduce any additional complexity to the visual tree since strings are by default presented by TextBlocks.
Otherwise you could create your own converter, or you could go with Fabio's solution and utilize Label.ContentStringFormat property.
Here's a way to bind to multiple properties :
a MultiBinding
an IMultiValueConverter
Code:
Imports System.Globalization
Imports System.Text
Class MainWindow
Public Shared ReadOnly Text1Property As DependencyProperty = DependencyProperty.Register(
"Text1", GetType(String), GetType(MainWindow), New PropertyMetadata(Nothing))
Public Property Text1 As String
Get
Return DirectCast(GetValue(Text1Property), String)
End Get
Set
SetValue(Text1Property, Value)
End Set
End Property
Public Shared ReadOnly Text2Property As DependencyProperty = DependencyProperty.Register(
"Text2", GetType(String), GetType(MainWindow), New PropertyMetadata(Nothing))
Public Property Text2 As String
Get
Return DirectCast(GetValue(Text2Property), String)
End Get
Set
SetValue(Text2Property, Value)
End Set
End Property
Private Sub MainWindow_OnLoaded(sender As Object, e As RoutedEventArgs)
Me.Text1 = "text1"
Me.Text2 = "text2"
End Sub
End Class
Converter:
Class MyConverter
Implements IMultiValueConverter
Public Function Convert(values As Object(), targetType As Type, parameter As Object, culture As CultureInfo) _
As Object Implements IMultiValueConverter.Convert
If values Is Nothing Then
Return DependencyProperty.UnsetValue
End If
Dim sb As New StringBuilder
If values.Length > 0 Then
sb.AppendLine(values(0))
End If
If values.Length > 1 Then
sb.AppendLine(values(1))
End If
Return sb.ToString()
End Function
Public Function ConvertBack(value As Object, targetTypes As Type(), parameter As Object, culture As CultureInfo) _
As Object() Implements IMultiValueConverter.ConvertBack
Throw New NotImplementedException
End Function
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
Loaded="MainWindow_OnLoaded"
mc:Ignorable="d">
<Grid>
<StackPanel>
<TextBox Text="{Binding ElementName=Window, Path=Text1, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding ElementName=Window, Path=Text2, UpdateSourceTrigger=PropertyChanged}" />
<Label>
<Label.Resources>
<local:MyConverter x:Key="MyConverter" />
</Label.Resources>
<Label.Content>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding ElementName="Window" Path="Text1" />
<Binding ElementName="Window" Path="Text2" />
</MultiBinding>
</Label.Content>
</Label>
</StackPanel>
</Grid>
</Window>
Related
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
I am trying to build a progressbar using MVVM. Basically, I have a main xaml and 2 UserControls, 1 for progressbar 1 for datagrid.
I am bit new and I followed this question and answer but I havent got any success. Below is my ViewModel code and Xaml code. Basically I have 2 problems,
1-How to bind CustomerModels or if even possible CustomerViewModel? I tried to use Itemsource binding direcly with ObservableCollection which I am filling with my delegateCommand that runs with a backgroundworker but no success. I tried without delegate and backgroundworker,simply using as below.
Me.myLoadCommand = New Commands.LoadCustomerModels()
What am I doing wrong?
<UserControl.Resources>
<vm:CustomerModelsVM x:Key="Customerobj"></vm:CustomerModelsVM>
</UserControl.Resources>
<Grid >
<DataGrid x:Name="grdData" ItemsSource="{Binding Path=CustomerModels}"/>
</Grid>
2-How to bind CurrentProgressBar? I tried to bind the progress bar status same way but I believe my ViewModel and Xaml somehow has no connection.
<UserControl x:Class="ucProgressBar"
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"
>
<Grid>
<ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}" Visibility="{Binding ProgressVisibility}"></ProgressBar>
<TextBlock Text="{Binding ElementName=myProgressBar, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Namespace ViewModels
Public Class CustomerModelsVM
Implements ICustomerModelsVM
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private ReadOnly worker As BackgroundWorker
Private m_currentProgress As Integer
Private _CustomerModels As New ObservableCollection(Of Models.CustomerModel)
Private mySaveCommand As ICommand
Private myLoadCommand As ICommand
Public Sub New()
Me.worker = New BackgroundWorker()
Me.myLoadCommand = New DelegateCommand(Sub() Me.worker.RunWorkerAsync(), AddressOf Progressisbusy)
' _CustomerModels = getCustomerModels()
Me.worker = New BackgroundWorker()
AddHandler Me.worker.DoWork, AddressOf Me.DoWork
AddHandler Me.worker.ProgressChanged, AddressOf Me.ProgressChanged
End Sub
Private Sub ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
Me.CurrentProgress = e.ProgressPercentage
End Sub
Private Function Progressisbusy() As Boolean
Return Not Me.worker.IsBusy
End Function
Private Sub OnPropertyChanged(Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public ReadOnly Property CustomerModels() As ObservableCollection(Of Models.CustomerModel)
Get
Return _CustomerModels
End Get
End Property
Public ReadOnly Property btnClick() As ICommand
Get
Return myLoadCommand
End Get
End Property
Public Property CurrentProgress() As Integer
Get
Return Me.m_currentProgress
End Get
Private Set(value As Integer)
If Me.m_currentProgress <> value Then
Me.m_currentProgress = value
OnPropertyChanged(Me.CurrentProgress)
End If
End Set
End Property
Private Sub DoWork(sender As Object, e As DoWorkEventArgs)
_CustomerModels = getCustomerModels()
End Sub
Function getCustomerModels() As ObservableCollection(Of Models.CustomerModel) Implements ICustomerModelsVM.GetCustomerModels
If _CustomerModels Is Nothing OrElse _CustomerModels.Count = 0 Then myLoadCommand.Execute(_CustomerModels)
Return _CustomerModels
End Function
You can add the viewmodel as DataContext of the main window which is holding the two user controls. Please refer the below code.
<UserControl x:Class="UserControl1"
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">
<Grid>
<DataGrid x:Name="grdData" Height="200" ItemsSource="{Binding Path=CustomerModels}"/>
</Grid>
</UserControl>
<UserControl x:Class="UserControl2"
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" >
<StackPanel>
<Button Command="{Binding LoadCommand}">Test</Button>
<ProgressBar Value="{Binding CurrentProgress, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="20" Width="200"
Visibility="{Binding ProgressVisibility}"></ProgressBar>
</StackPanel>
</UserControl>
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StakOveflw"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<local:UserControl1/>
<local:UserControl2/>
</StackPanel>
</Grid>
</Window>
Class MainWindow
Public Sub New()
InitializeComponent()
Me.DataContext = New CustomerModelsVM()
End Sub
End Class
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports Microsoft.Practices.Prism.Commands
Imports System.Threading
Public Class CustomerModelsVM
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private ReadOnly worker As BackgroundWorker
Private m_currentProgress As Integer
Private mySaveCommand As ICommand
Private myLoadCommand As ICommand
Public Property LoadCommand() As ICommand
Get
Return myLoadCommand
End Get
Set(ByVal value As ICommand)
myLoadCommand = value
End Set
End Property
Public Sub New()
Me.worker = New BackgroundWorker()
_CustomerModels = New ObservableCollection(Of CustomerModel)()
AddHandler Me.worker.DoWork, AddressOf Me.DoWork
AddHandler Me.worker.ProgressChanged, AddressOf Me.ProgressChanged
Me.worker.WorkerReportsProgress = True
myLoadCommand = New DelegateCommand(AddressOf LoadClick)
' _CustomerModels = getCustomerModels()
End Sub
Private Sub LoadClick()
Me.worker.RunWorkerAsync()
End Sub
Private Sub ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
CurrentProgress = e.ProgressPercentage
End Sub
Private Function Progressisbusy() As Boolean
Return Not Me.worker.IsBusy
End Function
Private Function CalculateProgress(total As Integer, complete As Integer) As Integer
' avoid divide by zero error
If total = 0 Then
Return 0
End If
' calculate percentage complete
Dim result = CDbl(complete) / CDbl(total)
Dim percentage = result * 100.0
' make sure result is within bounds and return as integer;
Return Math.Max(0, Math.Min(100, CInt(Math.Truncate(percentage))))
End Function
Private Sub OnPropertyChanged(Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private _CustomerModels As ObservableCollection(Of CustomerModel)
Public Property CustomerModels() As ObservableCollection(Of CustomerModel)
Get
Return _CustomerModels
End Get
Set(ByVal value As ObservableCollection(Of CustomerModel))
_CustomerModels = value
End Set
End Property
Public Sub GetCustomers()
Dim total As Integer
total = 10000
For index = 1 To total
Dim a As CustomerModel = New CustomerModel()
a.NewProperty = "test" + index.ToString()
Application.Current.Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Background, Function()
_CustomerModels.Add(a)
End Function)
worker.ReportProgress(CalculateProgress(total, index))
Next
End Sub
Public ReadOnly Property btnClick() As ICommand
Get
Return myLoadCommand
End Get
End Property
Public Property CurrentProgress() As Integer
Get
Return Me.m_currentProgress
End Get
Private Set(value As Integer)
Me.m_currentProgress = value
OnPropertyChanged("CurrentProgress")
End Set
End Property
Private Sub DoWork(sender As Object, e As DoWorkEventArgs)
_CustomerModels = getCustomerModels()
End Sub
Function getCustomerModels() As ObservableCollection(Of CustomerModel)
GetCustomers()
'Application.Current.Dispatcher.BeginInvoke(Windows.Threading.DispatcherPriority.Normal, New Action(Of Integer)(AddressOf GetCustomers), 3)
Return _CustomerModels
End Function
End Class
Public Class CustomerModel
Private newPropertyValue As String
Public Property NewProperty() As String
Get
Return newPropertyValue
End Get
Set(ByVal value As String)
newPropertyValue = value
End Set
End Property
End Class
I would like answer my question with a working solution. In my case problem was simply, I had to use dispatcher to clear my oberservable collection. So my Do_work function looks like as following. I didnt clear the observable collection before I start binding. Adding this simple line makes my code working.
Private Sub DoWork(sender As Object, e As DoWorkEventArgs)
Application.Current.Dispatcher.BeginInvoke(Sub() Me.CustomerModels.Clear())
' Me.CustomerModels.Clear()
For index = 1 To 100
Dim CustomerModel As New CustomerModel With { _
.age = 30 + index, _
.name = "testName" & index, _
.surname = "testSurname" & index, _
.Id = index}
Application.Current.Dispatcher.BeginInvoke(Sub() CustomerModels.Add(CustomerModel))
' CustomerModels.Add(CustomerModel)
Thread.Sleep(100)
worker.ReportProgress(CalculateProgress(100, index))
Next
End Sub
I am using the DataGrid that comes in the WpfToolkit for .NET 3.5. I am displaying DataTable. I want negative amounts to be red. The number of columns is variable so AutoGenerateColumns is True.
Following the advice here http://wpftutorial.net/DataGrid.html I have done the following. However whenever TemplateSelector is called item is a DataRowView and not cell contents. How can I reference the cell contents??
Public Class AmountDataGrid
Inherits Microsoft.Windows.Controls.DataGrid
Public Property CellTemplateSelector() As DataTemplateSelector
Get
Return DirectCast(GetValue(CellTemplateSelectorProperty), DataTemplateSelector)
End Get
Set(value As DataTemplateSelector)
SetValue(CellTemplateSelectorProperty, value)
End Set
End Property
Public Shared ReadOnly CellTemplateSelectorProperty As DependencyProperty = DependencyProperty.Register("Selector", GetType(DataTemplateSelector), GetType(AmountDataGrid), New FrameworkPropertyMetadata(Nothing))
Protected Overrides Sub OnAutoGeneratingColumn(e As Microsoft.Windows.Controls.DataGridAutoGeneratingColumnEventArgs)
e.Cancel = True
Columns.Add(New Microsoft.Windows.Controls.DataGridTemplateColumn() With {.Header = e.Column.Header, .CellTemplateSelector = CellTemplateSelector})
End Sub
End Class
DataGrid in XAML
<l:AmountDataGrid x:Name="dgMonths" Style="{StaticResource MonthsView}" CellTemplateSelector="{StaticResource TemplateSelector}"/>
Selector
Public Class TemplateSelector
Inherits DataTemplateSelector
Private _defaultTemplate As DataTemplate
Public Property DefaultTemplate() As DataTemplate
Get
Return _defaultTemplate
End Get
Set(value As DataTemplate)
_defaultTemplate = value
End Set
End Property
Public Overrides Function SelectTemplate(item As Object, container As System.Windows.DependencyObject) As DataTemplate
Return Me.DefaultTemplate
End Function
End Class
Code that binds the DataTable
strSelect = "TRANSFORM Format(Sum(Items.amount),'#,##0.00') AS total SELECT Accounts.accCategory, Accounts.ID, Accounts.comment AS Account FROM Accounts INNER JOIN Items ON Accounts.ID = Items.accFrom WHERE (((Year([idate]))=2013) AND ((Items.category)<>3 Or (Items.category) Is Null) AND ((Accounts.accCategory)=6 OR (Accounts.accCategory)=7) AND ((Accounts.curr)=1)) GROUP BY Accounts.accCategory, Accounts.ID, Accounts.comment PIVOT Format(idate,'mmm') IN ('Jan','Feb','Mar','Apr', 'May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')"
dsmcmd = New OleDbDataAdapter(strSelect, cn)
dsmcmd.Fill(dsm, "Totals")
dgMonths.ItemsSource = dsm.Tables("Totals").DefaultView
Thanks
Andy
Figured it out.... rather complicated for something that should really be simple!
XAML
<Window.Resources>
<l:DGAmountConverter x:Key="DGAmountConverter"/>
<l:DGColourConverter x:Key="DGColourConverter"/>
<DataTemplate x:Key="ColourAmount">
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dg:DataGridCell}, Converter={StaticResource DGAmountConverter}}" Foreground="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dg:DataGridCell}, Converter={StaticResource DGColourConverter}}"/>
</Grid>
</DataTemplate>
<l:TemplateSelector x:Key="TemplateSelector" DefaultTemplate="{StaticResource ColourAmount}"/>
</Window.Resources>
....
<l:AmountDataGrid x:Name="dgMonths" Style="{StaticResource MonthsView}" CellTemplateSelector="{StaticResource TemplateSelector}"/>
Code behind
Public Class AmountDataGrid
Inherits Microsoft.Windows.Controls.DataGrid
Public Property CellTemplateSelector() As DataTemplateSelector
Get
Return DirectCast(GetValue(CellTemplateSelectorProperty), DataTemplateSelector)
End Get
Set(value As DataTemplateSelector)
SetValue(CellTemplateSelectorProperty, value)
End Set
End Property
Public Shared ReadOnly CellTemplateSelectorProperty As DependencyProperty = DependencyProperty.Register("Selector", GetType(DataTemplateSelector), GetType(AmountDataGrid), New FrameworkPropertyMetadata(Nothing))
Protected Overrides Sub OnAutoGeneratingColumn(e As Microsoft.Windows.Controls.DataGridAutoGeneratingColumnEventArgs)
e.Cancel = True
Columns.Add(New Microsoft.Windows.Controls.DataGridTemplateColumn() With {.Header = e.Column.Header, .CellTemplateSelector = CellTemplateSelector})
End Sub
End Class
Public Class TemplateSelector
Inherits DataTemplateSelector
Private _defaultTemplate As DataTemplate
Public Property DefaultTemplate() As DataTemplate
Get
Return _defaultTemplate
End Get
Set(value As DataTemplate)
_defaultTemplate = value
End Set
End Property
Public Overrides Function SelectTemplate(item As Object, container As System.Windows.DependencyObject) As DataTemplate
Return Me.DefaultTemplate
End Function
End Class
Class DGAmountConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
Dim cell As Microsoft.Windows.Controls.DataGridCell = TryCast(value, Microsoft.Windows.Controls.DataGridCell)
If cell IsNot Nothing Then
Dim colNum As Integer = cell.Column.DisplayIndex
Dim cp As ContentPresenter = TryCast(cell.Content, ContentPresenter)
Dim view As DataRowView = TryCast(cp.Content, DataRowView)
If view IsNot Nothing Then
Return TryCast(view.Row.ItemArray(colNum), Object)
End If
End If
Return DependencyProperty.UnsetValue
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Nothing
End Function
End Class
Class DGColourConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
Dim cell As Microsoft.Windows.Controls.DataGridCell = TryCast(value, Microsoft.Windows.Controls.DataGridCell)
If cell IsNot Nothing Then
Dim colNum As Integer = cell.Column.DisplayIndex
Dim cp As ContentPresenter = TryCast(cell.Content, ContentPresenter)
Dim view As DataRowView = TryCast(cp.Content, DataRowView)
If view IsNot Nothing Then
Dim obj As Object = view.Row.ItemArray(colNum)
If obj Is Nothing OrElse Val(obj) >= 0 Then
Return New SolidColorBrush(Colors.White)
Else
Return New SolidColorBrush(Colors.Red)
End If
End If
End If
Return DependencyProperty.UnsetValue
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Nothing
End Function
End Class
I have a CustomerOrder class which has 2 properties namely CustomerName and NumberOfOrders. I have a Usercontrol to show these two properties, namely ucCustomerOrder.
Following the code corresponding
Customer Class
Public Class CustomerOrder
Private m_CustomerName As String = String.Empty
Private m_NumberOfOrders As Decimal = 0
Public Property CustomerName As String
Get
Return m_CustomerName
End Get
Set(ByVal value As String)
m_CustomerName = value
End Set
End Property
Public Property NumberOfOrders As Decimal
Get
Return m_NumberOfOrders
End Get
Set(ByVal value As Decimal)
m_NumberOfOrders = value
End Set
End Property
End Class
UserControl XAML
<UserControl x:Class="ucCustomerOrder"
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="99" d:DesignWidth="300">
<Grid>
<Label Name="lblCustomerName" Content="{Binding Path=CustomerName}" Height="28" HorizontalAlignment="Left" Margin="23,12,0,0" VerticalAlignment="Top" Width="237" />
<Label Name="lblNoOfOrders" Content="{Binding Path=NumberOfOrders}" Height="28" HorizontalAlignment="Left" Margin="23,46,0,0" VerticalAlignment="Top" Width="237" />
</Grid>
</UserControl>
Code behind the User control is as follows.
Public Class ucCustomerOrder
Private m_CustomerOrder As CustomerOrder
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
m_CustomerOrder = New CustomerOrder
Me.DataContext = m_CustomerOrder
End Sub
Public Property CustomerOrder As CustomerOrder
Get
Return m_CustomerOrder
End Get
Set(ByVal value As CustomerOrder)
m_CustomerOrder = value
Me.DataContext = m_CustomerOrder
End Set
End Property
End Class
In my main window I have a combo box control and ucCustomerOrder (User Control) and also I have list of Customer’s orders in Dictionary object. Like as follows
XAML For main windows looks like
<Window x:Class="DictionaryTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DictionaryTest" Height="182" Width="300" xmlns:my="clr-namespace:TestUserControls">
<Grid>
<ComboBox Height="23" Width="254" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="12,12,0,0" Name="cboCustomerOrders" ItemsSource="{Binding}" DisplayMemberPath="Key" SelectedValuePath="Value"/>
<my:ucCustomerOrder HorizontalAlignment="Left" VerticalAlignment="Top" Margin="12,56,0,0" x:Name="UcCustomerOrder1"/>
</Grid>
</Window>
Code Behind for my main window as follows.
Public Class DictionaryTest
Private Function GetCustomers() As Dictionary(Of String, CustomerOrder)
Dim customerList As New Dictionary(Of String, CustomerOrder)
Dim customer1 As New CustomerOrder
With customer1
.CustomerName = "XXXX"
.NumberOfOrders = 100
End With
Dim customer2 As New CustomerOrder
With customer2
.CustomerName = "ZZZZ"
.NumberOfOrders = 150
End With
customerList.Add("X-Customer", customer1)
customerList.Add("Z-Customer", customer2)
Return customerList
End Function
Private Sub DictionaryTest_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Try
cboCustomerOrders.ItemsSource = GetCustomers()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
End Class
How can I set my selected CustomerOrder object to the user control using Binding?
On a WPF Window, I have a simple listbox of decimal values which have an ObservableCollection of Amounts bound to it, a label bound to a Total property that shows the sum of the values below the ListBox, and a TextBox out to the right of the ListBox bound to the selectedItem.Amount property.
When I click on an item in the ListBox I want to be able to edit the selectedItem's value in the textbox that gets populated, tab off the TextBox, and have the listBoxItem update its value and I want the sum to be updated in the Label as well.
I understand how element-to-element databinding works (i.e. ListBox to Textbox)
What I am having trouble figuring out is element-to-object databinding(i.e. ListBox/ObservableCollection to the Total property)
Thanks so much!
Here are the two simple classes I have so far:
Public Class TransactionModel
Implements INotifyPropertyChanged
'Public Property Amount As Decimal
Private _amount As Decimal
Public Property Amount As Decimal
Get
Return _amount
End Get
Set(ByVal value As Decimal)
_amount = value
OnPropertyChanged(New PropertyChangedEventArgs("Amount"))
End Set
End Property
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
If Not e Is Nothing Then
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
End Class
Public Class ViewModel
Implements INotifyPropertyChanged
Private oc As ObservableCollection(Of TransactionModel)
Sub New()
oc = New ObservableCollection(Of TransactionModel)
oc.Add(New TransactionModel With {.Amount = 10.0})
oc.Add(New TransactionModel With {.Amount = 20.0})
oc.Add(New TransactionModel With {.Amount = 30.0})
oc.Add(New TransactionModel With {.Amount = 40.0})
End Sub
Public Function GetAmounts() As ObservableCollection(Of TransactionModel)
Return oc
End Function
Private _total As Decimal = 0.0
Public Property Total As Decimal
Get
For Each o In oc
_total += o.Amount
Next
Return _total
End Get
Set(ByVal value As Decimal)
_total = value
OnPropertyChanged(New PropertyChangedEventArgs("Total"))
End Set
End Property
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
If Not e Is Nothing Then
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
End Class
The first part is to add a TextBox, and bind it to the SelectedItem property on the ListBox. This will cause the TextBox to show the amount for the selected item, and allow the user to update it's value:
<TextBox DataContext="{Binding ElementName=lb1, Path=SelectedItem}"
Text="{Binding Path=Amount}" />
You will then need a property on ViewModel named TotalAmount, and a TextBlock bound to it's value. You will also have to handle PropertyChanged for "Value" and re-raise the event for "TotalAmount", which will cause the View to refresh:
<TextBlock Text="{Binding Path=TotalAmount}"></TextBlock>
and the event handler:
Public Class ViewModel Implements INotifyPropertyChanged
...
Public Sub New()
items = New ObservableCollection(Of TransactionModel)()
Dim tm As New TransactionModel()
tm.PropertyChanged += New PropertyChangedEventHandler(TransactionModel_PropertyChanged)
items.Add(tm)
tm = New TransactionModel()
tm.PropertyChanged += New PropertyChangedEventHandler(TransactionModel_PropertyChanged)
items.Add(tm)
tm = New TransactionModel()
tm.PropertyChanged += New PropertyChangedEventHandler(TransactionModel_PropertyChanged)
items.Add(tm)
End Sub
Private Sub TransactionModel_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
If e.PropertyName = "Amount" Then
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("TotalAmount"))
End If
End Sub
...
End Class
what you probably want to do is to bind your TextBox and your label to the SelectedItem property of the ListBox
here is some XAML to show you what I mean....
<StackPanel>
<TextBlock Width="248" Height="24" Text="Colors:" TextWrapping="Wrap"/>
<ListBox x:Name="lbColor" Width="248" Height="56">
<ListBoxItem Content="Blue"/>
<ListBoxItem Content="Green"/>
<ListBoxItem Content="Yellow"/>
<ListBoxItem Content="Red"/>
<ListBoxItem Content="Purple"/>
<ListBoxItem Content="Orange"/>
</ListBox>
<TextBlock Width="248" Height="24" Text="You selected color:" />
<TextBlock Width="248" Height="24" Text={Binding ElementName="lbColor"}/>
</StackPanel>
to make your label the sum of the values, you could use a converter and bind directly to the collection.