Improve performance moving multiple wpf elements - wpf

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.

Related

UI Freezes When Try to Update textbox in wpf

I have a code that has a text box and a button, when i press the button, the code update the text box and after words sleep for 5 seconds, in those seconds the ui is freezing
I tried with delegates, and threading but nothing works
This is the code with delegates:
MainWindow.vb
Class MainWindow
Delegate Sub MySubDelegate(ByVal x As String)
Private m_engine As Engine
Public Sub New()
m_engine = New Engine(AddressOf WriteToLog)
InitializeComponent()
Me.DataContext = m_engine.GetViewModel()
End Sub
Public Sub WriteToLog(str As String)
Dim vv As ViewModel = CType(DataContext, ViewModel)
vv.Log = str
End Sub
Private Sub clicked(sender As Object, e As RoutedEventArgs)
m_engine.TimingRecord()
End Sub
End Class
Engine.vb
Private m_viewModel As New ViewModel
Private _msd As MainWindow.MySubDelegate
Public Sub New()
End Sub
Sub New(msd As MainWindow.MySubDelegate)
_msd = msd
End Sub
Public Function GetViewModel() As ViewModel
Return m_viewModel
End Function
Public Sub TimingRecord()
_msd("aaaaa")
SetText()
End Sub
Public Sub SetText()
Thread.Sleep(5000)
End Sub
viewModel.vb
Public Class ViewModel
Implements INotifyPropertyChanged
Private m_log As String
Public Property Log As String
Get
Return m_log
End Get
Set(value As String)
m_log = value
End Set
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
This is the code with thread:
MainWindow.vb
Delegate Sub MySubDelegate(ByVal x As String)
Private m_engine As Engine
Public Sub New()
m_engine = New Engine(AddressOf WriteToLog)
InitializeComponent()
Me.DataContext = m_engine.GetViewModel()
End Sub
Public Sub WriteToLog(str As String)
Me.Dispatcher.Invoke(Sub() CType(DataContext, ViewModel).Log = str)
'Dim vv As ViewModel = CType(DataContext, ViewModel)
'vv.Log = str
End Sub
Private Sub clicked(sender As Object, e As RoutedEventArgs)
m_engine.TimingRecord()
End Sub
Engine.vb
Public Class Engine
Private m_viewModel As New ViewModel
Private m_thread As Thread
Private _msd As MainWindow.MySubDelegate
Public Sub New(msd As MainWindow.MySubDelegate)
_msd = msd
End Sub
Public Function GetViewModel() As ViewModel
Return m_viewModel
End Function
Public Sub TimingRecord()
m_thread = New Thread(AddressOf DoRecordThread)
m_thread.IsBackground = True
m_thread.Start("aa")
SetText()
End Sub
Public Sub SetText()
Thread.Sleep(5000)
End Sub
Private Sub DoRecordThread(str As String)
_msd(str)
'm_viewModel.Log = str
End Sub
End Class
The ViewModel Stay The same. I'm using wpf so i bind the Log into the textbox and with simple code it works.
Thank you all in advance.
Thread.Sleep causing the issue. It blocks the UI thread that makes the UI frozen.
Have the long running task in different thread and once completed Update the UI(TextBox) with Application.Current.Dispatcher.
Sample Code to update the UI from background thread with Dispatcher :
Application.Current.Dispatcher.Invoke(new Action(() => { Textbox1.Text = result; }))
This is not a right way but I hope this is what you are looking for.
Update
Also, there is another way, you can use builtin BackgroundWorker.
//Add the namespace
using System.ComponentModel;
//Declare
private readonly BackgroundWorker worker = new BackgroundWorker();
//Initialize
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
//method declaration
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
worker.ReportProgress(0, "Process started");
// run all background tasks here
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Progress update with e.UserState.ToString();
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
//update ui once worker complete his work
}
//Invoke
worker.RunWorkerAsync();
The problem solved by put the thread.sleep but very foolish of me in the viewmodel class i accidently delete the property changed now it is working perfectly thank you all for helping

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

How to bind Progressbar and Datagrid at the same time using MVVM?

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

Programmatically bind an image's canvas point in 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

WPF Datagrid DataGridComboBoxColumn Autogenerate at runtime

I have a datagrid in my vb.net 4.0 wpf project. I have seen many examples in XAML on how to bind a DataGridComboBoxColumn however I need to do this in code as I have auto-generating columns. I switch the datagridbinding source to multiple data sets.
Inside some of these custom classes are a couple lists. I can get text and checkboxes to auto-generate correctly. When it comes across my array (I have tried many different kinds) I just see a textboxcolumn with the words (Collection) in it.
For instance - One screen I am grabbing system information on WMI calls. One of the calls returns all IP addresses on the server (We can have up to 8 IP addresses) I do not want a column per IP address. I would like to include a list of those into the datagrid so you can drop down and see them.
Any suggestions on if this is possible or if I am doing something wrong?
Thank you
Sample Code
Imports System.Collections.ObjectModel
Class MainWindow
Dim ServerInfoArray As ObservableCollection(Of ServerInfo) = New ObservableCollection(Of ServerInfo)
Private ReadOnly _ipAddresses As ObservableCollection(Of String) = New ObservableCollection(Of String)
Private Sub GetInfo(ByVal list As List(Of String))
For Each server As String In list
Dim tempip As List(Of String) = New List(Of String)
Dim sinfo As ServerInfo = New ServerInfo
tempip.Add("192.129.123.23")
tempip.Add("23.213.223.21")
sinfo.IPArray = tempip
sinfo.Servername = server
ServerInfoArray.Add(sinfo)
Next
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim serverlist As List(Of String) = New List(Of String)
serverlist.Add("Test")
serverlist.Add("Random")
serverlist.Add("Local")
GetInfo(serverlist)
End Sub
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim Col_Serial As DataGridTextColumn = New DataGridTextColumn()
Col_Serial.Binding = New Binding("Servername")
Col_Serial.Header = "Servername"
Col_Serial.Width = 40
Dim Col_IPArray = New DataGridComboBoxColumn()
Col_IPArray.Header = "IP Addresses"
Col_IPArray.IsReadOnly = True
Col_IPArray.ItemsSource = Me._ipAddresses
Col_IPArray.SelectedItemBinding = New Binding("IPArray")
DataGrid1.Columns.Add(Col_Serial)
DataGrid1.Columns.Add(Col_IPArray)
DataGrid1.ItemsSource = ServerInfoArray
End Sub
End Class
Class ServerInfo
Dim _Servername As String
Dim _IPArray As List(Of String)
Public Property Servername() As String
Get
Return _Servername
End Get
Set(ByVal value As String)
_Servername = value
End Set
End Property
Public Property IPArray As List(Of String)
Get
Return _IPArray
End Get
Set(ByVal value As List(Of String))
_IPArray = value
End Set
End Property
Public Sub New()
_Servername = Nothing
_IPArray = New List(Of String)
End Sub
End Class
Not sure if I am understanding perfectly but if you can live with AutoGenerateColumns = False then you can manipulate the columns, their properties and bindings from code like below. So you could use whatever logic you need to in order setup the columns from code. I tried to show how you can source the combobox column items from a different object than the items in the DataGrid as well.
This is meant to be a simple example so I just did everything in the code-behind but from an MVVM standpoint, it depends on your preferred approach but possibility is that you could accomplish the same logic from a controller class that spins up your view models, etc...
Hope it helps!
XAML...
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="btnRefreshIPList">Refresh list of IPs</Button>
<DataGrid x:Name="dataGrid1"></DataGrid>
</StackPanel>
</Window>
Code behind...
Imports System.Collections.ObjectModel
Class MainWindow
'The list of IPs for column's ItemSource property
Private ReadOnly _ipAddresses As ObservableCollection(Of String)
'The items for binding to the DataGrid's ItemsSource
Private _items As List(Of MyObjectWithIPAddress)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_ipAddresses = New ObservableCollection(Of String)
_items = New List(Of MyObjectWithIPAddress)
End Sub
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Me.dataGrid1.AutoGenerateColumns = False
dataGrid1.Columns.Clear()
'Example of text column (Text bound to Name property)
Dim dgTxtCol = New DataGridTextColumn()
dgTxtCol.Header = "Name"
dgTxtCol.Binding = New Binding("Name")
dataGrid1.Columns.Add(dgTxtCol)
'Example of combobox column (SelectedItem bound to IPAddress)
Dim dgCmbCol = New DataGridComboBoxColumn()
dgCmbCol.Header = "IP Address"
dgCmbCol.ItemsSource = Me._ipAddresses
dgCmbCol.SelectedItemBinding = New Binding("IPAddress")
dataGrid1.Columns.Add(dgCmbCol)
'Add items to DataGrid
_items.Add(New MyObjectWithIPAddress("foo1"))
_items.Add(New MyObjectWithIPAddress("foo2"))
Me.dataGrid1.ItemsSource = Me._items
End Sub
''' <summary>
''' To emulate fetching the object that has the IP list
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Private Function GetIpList() As MyObjectWithListOfIPs
Dim inst = New MyObjectWithListOfIPs
inst.IPList = New List(Of String)(New String() {"10.0.0.1", "10.0.0.2", "10.0.0.3"})
Return inst
End Function
''' <summary>
''' Updates the ObservableCollection instance based on business object
''' </summary>
''' <remarks></remarks>
Private Sub RefreshIpAddresses()
_ipAddresses.Clear()
For Each ip As String In GetIpList().IPList
_ipAddresses.Add(ip)
Next
End Sub
Private Sub btnRefreshIPList_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnRefreshIPList.Click
RefreshIpAddresses()
End Sub
End Class
''' <summary>
''' Object with response data (e.g., list of IPs)
''' </summary>
''' <remarks></remarks>
Class MyObjectWithListOfIPs
Private _ipList As List(Of String)
Public Property IPList() As List(Of String)
Get
Return _ipList
End Get
Set(ByVal value As List(Of String))
_ipList = value
End Set
End Property
End Class
''' <summary>
''' Disperate object that "has an" address
''' </summary>
''' <remarks></remarks>
Class MyObjectWithIPAddress
Public Sub New(name As String)
Me._name = name
End Sub
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _ipAddress As String
Public Property IPAddress() As String
Get
Return _ipAddress
End Get
Set(ByVal value As String)
_ipAddress = value
End Set
End Property
End Class

Resources