Binding an ObservableCollection Dependency Property in a User Control - wpf

I have a UserControl that hosts a TreeView that has a custom collection which inherits from ObservableCollection of a custom class. I am trying to bind to a property using a dependency property from a ViewModel, whilst I have proven the process works for another property it does not work here.
I'm sure it is something silly I have missed but I've been going round in circles, can anyone spot my mistake?
The UserControl XAML:
<UserControl
x:Class="FileExplorer.TreeViewUserControl"
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:fe="clr-namespace:WpfControlLibrary.FileExplorer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="FileExplorer"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
<TreeView
BorderThickness="0"
ItemsSource="{Binding FileSystem, RelativeSource={RelativeSource AncestorType=UserControl}}"
TreeViewItem.Collapsed="TreeView_Collapsed"
TreeViewItem.Expanded="TreeView_Expanded"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type fe:FileSystemObject}" ItemsSource="{Binding Items}">
<StackPanel Margin="0,2" Orientation="Horizontal">
<Image
Width="14"
Margin="2"
Source="{Binding Image}"
Stretch="Fill" />
<TextBlock
VerticalAlignment="Center"
FontSize="{StaticResource FontSizeSmall}"
Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The code behind for the UserControl:
Namespace FileExplorer
Public Class TreeViewUserControl
Inherits UserControl
#Region "Constructors"
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.DataContext = Me
End Sub
#End Region
#Region "Properties"
Public Shared ReadOnly FileSystemProperty As DependencyProperty = DependencyProperty.Register("FileSystem", GetType(FileSystemObjectCollection), GetType(TreeViewUserControl))
Public Property FileSystem As FileSystemObjectCollection
Get
Return CType(GetValue(FileSystemProperty), FileSystemObjectCollection)
End Get
Set(ByVal value As FileSystemObjectCollection)
SetValue(FileSystemProperty, value)
FileExplorer.UpdateLayout()
End Set
End Property
#End Region
Private Function GetFileSystemInfo(ByVal root As String) As FileSystemObjectCollection
Dim items As New FileSystemObjectCollection
' Parse all the directories at the path
For Each dir As String In Directory.GetDirectories(root)
items.Add(New FileSystemObject(dir, root))
Next
' Parse all the file at the path
For Each file As String In Directory.GetFiles(root)
items.Add(New FileSystemObject(file, root))
Next
Return items
End Function
Private Sub TreeView_Collapsed(sender As Object, e As RoutedEventArgs)
Dim node As TreeViewItem = CType(e.OriginalSource, TreeViewItem)
Dim fs As FileSystemObject = CType(node.DataContext, FileSystemObject)
fs.Clear()
End Sub
Private Sub TreeView_Expanded(sender As Object, e As RoutedEventArgs)
Dim node As TreeViewItem = CType(e.OriginalSource, TreeViewItem)
Dim fs As FileSystemObject = CType(node.DataContext, FileSystemObject)
If Not fs.HasChildren Then Exit Sub
fs.Items = GetFileSystemInfo(fs.FullName)
End Sub
End Class
End Namespace
The MainWindow XAML:
<Window
x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fe="clr-namespace:WpfControlLibrary.FileExplorer;assembly=WpfControlLibrary"
xmlns:local="clr-namespace:ModStudio.Client"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:ModStudio.Client.ViewModels"
Title="MainWindow"
d:DesignHeight="600"
d:DesignWidth="800"
WindowStartupLocation="CenterOwner"
WindowState="Maximized"
mc:Ignorable="d">
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainWindowViewModel" />
</Window.Resources>
<Grid>
<fe:TreeViewUserControl DataContext="{StaticResource MainWindowViewModel}" FileSystem="{Binding ApplicationExplorer}" />
</Grid>
</Window>
The MainWindow has its DataContext set to a new instance of the ViewModel in code behind. The MainWindowViewModel has a property called ApplicationExplorer which is an instance of FileSystemObjectCollection. As mentioned FileSystemObjectCollection inherits from ObservableCollection(Of FileSystemObject). A FileSystemObject implements INotifyPropertyChanged. If I change ApplicationExplorer property, the control remains blank.
I have intentionally omitted some code here, but can add them if necessary.

Don't set the DataContext in the UserControl, i.e. remove this line:
Me.DataContext = Me
When you explcitly set the DataContext like this, you break the inheritance chain which means that the binding to the ApplicationExplorer property in your window no longer works:
<fe:TreeViewUserControl DataContext="{StaticResource MainWindowViewModel}"
FileSystem="{Binding ApplicationExplorer}" />

I made 3 changes and got this working!
Added OnPropertyChanged to ApplicationExplorer:
Public Property ApplicationExplorer As FileSystemObjectCollection
Get
Return _applicationExplorer
End Get
Set(value As FileSystemObjectCollection)
_applicationExplorer = value
OnPropertyChanged(NameOf(ApplicationExplorer))
End Set
End Property
Updated the binding in the UserControl:
<UserControl
x:Class="FileExplorer.TreeViewUserControl"
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:fe="clr-namespace:WpfControlLibrary.FileExplorer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="FileExplorer"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
<TreeView
BorderThickness="0"
ItemsSource="{Binding FileSystem, RelativeSource={RelativeSource AncestorType=fe:TreeViewUserControl}}"
TreeViewItem.Collapsed="TreeView_Collapsed"
TreeViewItem.Expanded="TreeView_Expanded"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type fe:FileSystemObject}" ItemsSource="{Binding Items}">
<StackPanel Margin="0,2" Orientation="Horizontal">
<Image
Width="14"
Margin="2"
Source="{Binding Image}"
Stretch="Fill" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>
Removed the Me.DataContext = Me from the UserControl code behind as you suggested

Related

How to set Data or Communicate with Property in UserControl As New Window?

I just Started with WPF MVVM but i stuck on a point. I want to define the problem as Point.
I created a UserControl with one ComboBox(Binded with "Data" Property) and One Button (UserControl1.xaml).
Added to UserControl1 to MainWindow.xaml in Grid As
Created a Class1.vb With Public Property with INOtifyPropertyChange
Binded using
Binded Button with ICommand.
Everything is Working as button is working , combobox showing Data Properly but....
I added another UserControl As UserControl2.Xaml
Added another ComboBox(binded with "Data1" property) in UserControl2
UserControl2 is also binded with Class1
On Button i am setting two things
i) Setting Data1 = Data (So value of Data set to Data1 on button click)
ii) Initializing the UserControl2 As Window by
Dim win As New Window with {.Content = New UserControl2}
10) On button click from UserControl1 the Window is Showing but Data is not there which is binded on Usercontrol2 Combobox
I have tried dataContext on code behind in xaml.vb
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 ></local:UserControl1>
</Grid>
</Window>
UserControl1.Xaml :-
<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"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext >
<local:Class1 ></local:Class1>
</UserControl.DataContext>
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="80,94,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding DataContext.Data,RelativeSource={RelativeSource AncestorType=UserControl } , UpdateSourceTrigger=PropertyChanged }"/>
<Button x:Name="button" Command="{Binding DataContext.TestBut ,RelativeSource={RelativeSource AncestorType=UserControl} , UpdateSourceTrigger=PropertyChanged }" Content="Button" HorizontalAlignment="Left" Margin="90,161,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</UserControl>
Class1.vb
Imports System.ComponentModel
Public Class Class1
Implements INotifyPropertyChanged
Private _data As String()
Private _data1 As String()
Private _testBut As ICommand
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChange(prop As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
End Sub
Public Property Data As String()
Get
Return _data
End Get
Set(value As String())
_data = value
OnPropertyChange("Data")
End Set
End Property
Public Property TestBut As ICommand
Get
Return New Command(AddressOf test)
End Get
Set(value As ICommand)
_testBut = value
OnPropertyChange("TestBut")
End Set
End Property
Public Property Data1 As String()
Get
Return _data1
End Get
Set(value As String())
_data1 = value
OnPropertyChange("Data1")
End Set
End Property
Private Sub test(obj As Object)
Data1 = Data
Dim win As New Window With {.Content = New UserControl2,
.DataContext = Data1}
win.Show()
End Sub
Public Sub New()
Data = New String() {"test", "test1", "test2", "test3"}
End Sub
End Class
UserControl2
<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"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<User
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="78,101,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding DataContext.Data1, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged }"/>
</Grid>
</UserControl>
I want to minimize the code in code behind (xaml.vb) as much possible as i want no code in xaml.vb, also I want to set communication between two usercontrol
i hope you can enlighten me on this problem
If you want to use the Same Data in both usercontrol why cant you bind same "Data" instead of binding "Data1" in UserControl2?
Please see the modification. I have assigned datacontext in UserControl2.xaml. And changed combobox itemsource binding to Data
<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"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext >
<local:Class1 ></local:Class1>
</UserControl.DataContext>
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="78,101,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding DataContext.Data, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged }"/>
</Grid>
</UserControl>
In the Function Test, i removed setting datacontext as we are already setting in xaml
Private Function test()
Dim win As New Window With {.Content = New UserControl2
}
win.Show()
End Function
I am able to populate combo box in usercontrol 2 .
Edit : Problem why you are not able to see the data with assigning "Data1", once you create the new usercontrol , new instance of view model will be created and you are assigning "Data1=Data" before creating new window instance .
I have modified the code as per your suggestion .
Private Function test()
Dim customer3 As Class1
customer3 = New Class1()
Dim win As New Window With {.Content = New UserControl2, .DataContext = customer3
}
Dim classObject = CType(win.DataContext, Class1)
classObject.Data1 = Data
win.Show()
End Function
Xaml will be using "Data1" object as itemsource
<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"
xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="78,101,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding DataContext.Data1, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged }"/>
</Grid>
</UserControl>
Please ignore poor naming

WPF User Control Data Binding Not Working

I'm creating a simple User Control combining a popup with a text view, nothing crazy. When I set it up in a window at first to style it all out, it worked perfectly, but when I moved it into a User Control to actually finish it up, it won't work correctly any more.
I pass a min and max value into the control and then it automatically creates a list of numbers to pick from in that range. In the User Control, the list of numbers doesn't get bound correctly, who knows why. Maybe someone can take a look at my code.
I've read a bunch of other questions about this, but don't really know what's happening. I'm not getting any errors in my output window, so no clues there. Anyway, here's the code -
UserControl.xaml
<UserControl x:Class="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Me"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBox Name="myTextbox"
Height="30"
Margin="0"
FontSize="14"
IsReadOnly="True"
Padding="5,2"
Text="{Binding Value}" />
<Popup x:Name="myPopup"
PlacementTarget="{Binding ElementName=myTextbox}"
StaysOpen="True">
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myTextbox, Path=IsFocused}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<StackPanel>
<ListView Name="myListView"
Height="100"
MaxHeight="300"
ItemsSource="{Binding List,
UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Label Width="100"
Height="30"
Margin="0"
Content="{Binding}"
FontFamily="Segoe UI"
FontSize="14"
Padding="5,2" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Width="200" Height="30" />
</StackPanel>
</Popup>
</StackPanel>
UserControl.xaml.vb
Imports System.ComponentModel
Imports System.Linq.Expressions
Imports System.Collections.ObjectModel
Class UserControl1
' Dependency Properties
Public Shared ReadOnly ListProperty As DependencyProperty = DependencyProperty.Register("List", GetType(ObservableCollection(Of Integer)), GetType(MainWindow))
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MaxValueProperty As DependencyProperty = DependencyProperty.Register("MaxValue", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MinValueProperty As DependencyProperty = DependencyProperty.Register("MinValue", GetType(Integer), GetType(MainWindow))
' Properties
Public Property List As ObservableCollection(Of Integer)
Get
Return DirectCast(GetValue(ListProperty), ObservableCollection(Of Integer))
End Get
Set(value As ObservableCollection(Of Integer))
SetValue(ListProperty, value)
End Set
End Property
Public Property Value As Integer
Get
Return DirectCast(GetValue(ValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Public Property MaxValue As Integer
Get
Return DirectCast(GetValue(MaxValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MaxValueProperty, value)
End Set
End Property
Public Property MinValue As Integer
Get
Return DirectCast(GetValue(MinValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MinValueProperty, value)
End Set
End Property
Private Sub ListView_SelectionChanged(sender As System.Object, e As System.Windows.Controls.SelectionChangedEventArgs)
Value = List(myListView.SelectedIndex)
End Sub
Private Sub UserControl1_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
List = New ObservableCollection(Of Integer)
' Add all available numbers into the list
For iCounter As Integer = MinValue To MaxValue
List.Add(iCounter)
Next
' Set the selected index on the list for the value
myListView.SelectedIndex = Value - MinValue
End Sub
End Class
Just for reference, when I tested this out, I set the Min and Max values myself in both the window and usercontrol setup.
I think that you have made the same mistake that I used to when I first started learning WPF. I can't guarantee that this is the cause of your problem, but as it's the only one that I can see, I'll address it. Unfortunately, there are so many poor tutorials and quick examples that show the connecting of a UserControl.DataContext to itself:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Or:
DataContext = this;
Now this is perfectly acceptable if you don't want to Bind to the UserControl externally because it's a quick and easy way to connect with properties defined in the code behind. However, when you want to Bind to the properties from outside the control, you'll find problems. In these instances (if not on all occasions), you should use a RelativeSource Binding to Bind to your code behind properties:
<TextBox Name="myTextbox" Height="30" Margin="0" FontSize="14" IsReadOnly="True"
Padding="5,2" Text="{Binding Value, RelativeSource={RelativeSource AncestorType={
x:Type UserControl1}}}" />
In this Binding, UserControl1 is the name of the UserControl that declared the properties. This should be done on all of the internal Bindings. This way, the DataContext is not set to the UserControl, but the Bindings still find the properties.
You can find out more about RelativeSource from the RelativeSource MarkupExtension page on MSDN.
As I cant provide a comment to Sheridan good answer I have to provide a new answer, sorry for this.
While I love this solution
DataContext="{Binding RelativeSource={RelativeSource Self}}"
it fails (as Sheridan pointed out already) fast.
What you can do is just set the DataContext of the content of your User Control
<UserControl x:Class="Example.View.Controls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Example.View.Controls"
mc:Ignorable="d">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:MyUserControl}}}">
</Grid>
In this way all following Bindings have less Boilerplate code as you can just bind directly to your DP from the code-behind like:
<Label Content="{Binding MyLabel}"/>
As for Windows Apps (Windows 8 and Windows 10 UWP) the way to go is to give your control a name and reference it within your XAML file using Path and ElementName:
<UserControl
x:Class="MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Control"
mc:Ignorable="d" >
<Grid Height="240" VerticalAlignment="Top">
<Rectangle Fill="{Binding ElementName=Control, Path=Background}" />
</Grid>
</UserControl>
``

How do I bind a WPF control to a VB.net property? [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Data Binding WPF Property to Variable
How would I bind my module1 property to my WPF TextBox1?
WPF code:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="210,146,0,0" Name="TextBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</Window>
VB.net code:
Module Module1
ReadOnly Property tbBinding As String
Get
Return "Success!"
End Get
End Property
End Module
Below is code that I have been working on based on the feed back I have been getting and the reading I have been doing.
/#######Current code in progres (trying with a class instead of a module)#######/
XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid DataContext="Class1">
<TextBox Height="23" HorizontalAlignment="Left" Margin="210,146,0,0" Name="TextBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=tbBinding2}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="192,74,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>
Class1:
Imports System.ComponentModel
Public Class Class1
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
Dim varField As String = String.Empty
Public Property tbBinding2 As String
Get
Return varField
End Get
Set(value As String)
varField = value
NotifyPropertyChanged("tbBinding2")
End Set
End Property
End Class
MainWindow:
Class MainWindow
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim myClass1 As New Class1
myClass1.tbBinding2 = "Success!"
End Sub
End Class
You are not setting the DataContext anywhere
WPF has two layers: the data layer and the UI layer. The data layer is null by default, and you can set it by setting the DataContext property of any UI objects. Bindings are used to pull data from the data layer into the UI layer.
So, if you say MainWindow.DataContext = new Class1(), then you are setting the data layer beind MainWindow to a new instance of your Class1 object.
Writing <TextBox Text="{Binding tbProperty}" /> in the XAML is telling WPF to look in the data layer for a property called tbProperty and use it for the Text value of the TextBox.
If you change the tbProperty in your Class1 object being used as the DataContext, that change will also be reflected in TextBox.Text (providing you implemented INotifyPropertyChanged). And if the binding mode is set to TwoWay (default for TextBox.Text), then changes to TextBox.Text will also update the tbProperty in the data layer.
I actually recently posted an overview of the DataContext on my blog if you're interested.
You need to implement the INotifyPropertyChanged interface. Refer to this article for an example.
Edit:
Here is an example of binding to class Class1 (a.k.a. the "view model") from XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels:"clr-namespace:MyApplication.ViewModels"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModels:Class1 />
</Window.DataContext>
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="210,146,0,0" Name="TextBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=tbBinding2}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="192,74,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>
Note that this XAML assumes that the Class1 class is contained in the same assembly under the MyApplication.ViewModels namespace.

Multiple instances of a Viewmodel

Im very new to the WPF and MVVM (this is my 1st project in WPF). The project I'm working on is supposed to accept some search criteria, and display the results in the grid. To construct the query I'm using Dynamic LINQ queries. I seem to have issues managing instances of my ProjectSearchViewModel which corresponds to the view that's responsible for collecting the search criteria and executing the query. One instance is created when I create MainWindowViewModel. This creates all other viewmodel instances. This is what I expect. But when time comes to Show the MainWindow, I get another ProjectSearchViewModel, I guess from the binding.
The general idea is this:
The search criteria are filled in the ProjectSearchView.
When Load Command is pressed, I send SearchResultMessage using Reactive Extensions method.
The message is picked up by MainWindowViewModel
MainWindowViewModel is querying the ProjectSearchViewModel.SearchResult and assigning the IObservable List to AllProjectsViewModel.AllProjects which is bound to a datagrid to show the results (AllProjectView is responsible to show the grid with resulting projects list)
The problem is that the parameter filling and sending of the SearchResultMessage happens in one instance of ProjectSearchViewModel and the actual querying of SearchResult from MainWindowViewModel happens in another instance, where all the search criteria are empty.
I guess I have no choice but to post my code: Here it the abridged version of it, omitting some iDisposable plumbing and such. For my model I use Entity Framework 4.
As I mentioned, I'm a total newbie so If anyone sees any blatant disregard for common sense, please set me straight.
Imports Cheminator.ViewModel
Partial Public Class App
Inherits Application
Private viewModel As MainWindowViewModel
Private window As MainWindow
Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
MyBase.OnStartup(e)
window = New MainWindow
viewModel = New MainWindowViewModel '1st instance of ProjectSearchViewModel created Here
window.DataContext = viewModel
window.Show() '2nd instance of ProjectSearchViewModel created Here
End Sub
End Class
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cheminator"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
xmlns:vw="clr-namespace:Cheminator.Views"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxnb="http://schemas.devexpress.com/winfx/2008/xaml/navbar"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
Title="DXWpfApplication" Height="600" Width="800"
dx:ThemeManager.ThemeName="Office2007Blue"
>
<Window.Resources>
<ResourceDictionary Source="MainWindowResources.xaml" />
</Window.Resources>
<dxd:DockLayoutManager>
<dxd:LayoutGroup>
<dxd:LayoutGroup Orientation="Vertical" Width="3*">
<dxd:DocumentGroup Height="3*" SelectedTabIndex="0">
<dxd:DocumentPanel Caption="Document1" Height="3*" >
<ContentControl
Content="{Binding Path=ProjectsVM}"
/>
</dxd:DocumentPanel>
</dxd:DocumentGroup>
<dxd:LayoutPanel Caption="Search Criteria" Height="*" CaptionImage="Images/Icons/DetailView.png">
<ContentControl
Content="{Binding Path=ProjectsSearchVM}"
/>
</dxd:LayoutPanel>
</dxd:LayoutGroup>
</dxd:LayoutGroup>
</dxd:DockLayoutManager>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cheminator"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
xmlns:vw="clr-namespace:Cheminator.Views" >
<DataTemplate DataType="{x:Type vm:AllProjectsViewModel}">
<vw:AllProjectsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ProjectSearchViewModel}">
<vw:ProjectSearchView />
</DataTemplate>
<UserControl x:Class="Cheminator.Views.AllProjectsView"
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:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<dxg:GridControl AutoPopulateColumns="True" ShowBorder="False" >
<dxg:GridControl.DataSource>
<Binding Path="AllProjects"/>
</dxg:GridControl.DataSource>
<dxg:GridControl.View>
<dxg:TableView>
</dxg:TableView>
</dxg:GridControl.View>
</dxg:GridControl>
</UserControl>
<UserControl x:Class="Cheminator.Views.ProjectSearchView"
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:vm="clr-namespace:Cheminator.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="470">
<Grid Height="160" Width="470">
<Grid.DataContext>
<vm:ProjectSearchViewModel />
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="175*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="Cotation ID:" Height="28" Margin="49,12,32,0" Name="Label1" VerticalAlignment="Top" />
<TextBox Grid.Column="1" Height="23" Margin="0,14,159,0" Name="CotationIDTextBox" VerticalAlignment="Top" Text="{Binding Path=CotationID, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="1" Content="Cotation Name:" Height="28" Margin="49,6,6,0" Name="Label2" VerticalAlignment="Top" />
<TextBox Grid.Row="1" Grid.Column="1" Height="23" Margin="0,8,159,0" Name="CotationNameTextBox" VerticalAlignment="Top" Text="{Binding Path=ProjectSummary, UpdateSourceTrigger=PropertyChanged}"/>
<Label Grid.Row="2" Content="User:" Height="28" Margin="49,6,32,0" Name="Label3" VerticalAlignment="Top" />
<TextBox Grid.Row="2" Grid.Column="1" Height="23" Margin="0,8,159,0" Name="UserTextBox" VerticalAlignment="Top" Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged}"/>
<Button
Command="{Binding Path=LoadCommand}"
Content="_Load"
HorizontalAlignment="Right"
Margin="0,10,51,12"
MinWidth="60" Grid.Row="3" Width="72" Grid.Column="1" />
</Grid>
</UserControl>
Public Class MainWindowViewModel
Inherits ViewModelBase
Private _commands As ReadOnlyCollection(Of CommandViewModel)
Private _ProjectsVM As AllProjectsViewModel
Private _ProjectsSearchVM As ProjectSearchViewModel
Public Sub New()
MyBase.DisplayName = "Cheminator"
_ProjectsSearchVM = New ProjectSearchViewModel
Messenger.[Default].OfType(Of SearchResultMessage) _
.Subscribe(Sub(param As SearchResultMessage)
ProjectsVM.AllProjects = ProjectsSearchVM.SearchResult
End Sub)
_ProjectsVM = New AllProjectsViewModel
End Sub
Public ReadOnly Property ProjectsVM As AllProjectsViewModel
Get
If (_ProjectsVM IsNot Nothing) Then
Return _ProjectsVM
End If
Return Nothing
End Get
End Property
Public ReadOnly Property ProjectsSearchVM As ProjectSearchViewModel
Get
If (_ProjectsSearchVM IsNot Nothing) Then
Return _ProjectsSearchVM
End If
Return Nothing
End Get
End Property
End Class
Public Class AllProjectsViewModel
Inherits ViewModelBase
Private m_ProjectsList As ObservableCollection(Of xGMV_Cotation)
Public Sub New()
MyBase.DisplayName = "Temp AllProjectsViewModel Name" 'Strings.AllProjectsViewModel_DisplayName
End Sub
Public Property AllProjects() As ObservableCollection(Of xGMV_Cotation)
Get
Return m_ProjectsList
End Get
Set(ByVal value As ObservableCollection(Of xGMV_Cotation))
m_ProjectsList = value
OnPropertyChanged("AllProjects")
End Set
End Property
End Class
Public Class ProjectSearchViewModel
Inherits ViewModelBase
Private m_ProjectsList As ObservableCollection(Of xGMV_Cotation)
Public Sub New()
MyBase.DisplayName = "Cheminator.ProjectSearchViewModel"
End Sub
Dim _CotationID As Integer
Public Property CotationID As Integer
Get
Return _CotationID
End Get
Set(ByVal value As Integer)
_CotationID = value
MyBase.OnPropertyChanged("CotationID")
End Set
End Property
Public Property ProjectSummary As String
Public Property UserName As String
Private m_LoadCommand As RelayCommand
Public ReadOnly Property LoadCommand As ICommand
Get
If m_LoadCommand Is Nothing Then
Dim LoadAction As New Action(Of Object)(AddressOf Me.Load)
m_LoadCommand = New RelayCommand(LoadAction)
End If
Return m_LoadCommand
End Get
End Property
Public ReadOnly Property SearchResult() As ObservableCollection(Of xGMV_Cotation)
Get
Dim xWhere As String = ""
Dim i As Integer = 0
Dim parameterList As New ArrayList
If Not String.IsNullOrEmpty(CotationID) Then
xWhere = String.Format("CotationID = #{0}", i)
parameterList.Add(CotationID)
i += 1
End If
If Not String.IsNullOrEmpty(ProjectSummary) Then
If i > 0 Then
xWhere = xWhere & " AND "
End If
xWhere = xWhere & String.Format("ProjectSummary = '#{0}'", i)
i += 1
parameterList.Add(ProjectSummary)
End If
If Not String.IsNullOrEmpty(UserName) Then
If i > 0 Then
xWhere = xWhere & " AND "
End If
xWhere = xWhere & String.Format("UserName = '#{0}'", i)
i += 1
parameterList.Add(UserName)
End If
Return New ObservableCollection(Of xGMV_Cotation)(DataContext.DBEntities.xGMV_Cotations.Where(xWhere, parameterList.ToArray))
End Get
End Property
Private Sub Load()
OnPropertyChanged("SearchResult")
Messenger.Default.Send(New SearchResultMessage())
End Sub
End Class
everytime you create a ProjectSearchView usercontrol, you also create a ProjectSearchViewModel too. you should remove the following from your usercontrl xaml.
<Grid.DataContext>
<vm:ProjectSearchViewModel />
</Grid.DataContext>
because of your datatemplate you do not need to set the datacontext for ProjectSearchView, it already has the right datacontext.

WPF -- Anyone know why I can't get this binding to reference?

<StackPanel x:Name="stkWaitingPatients" Width="300" Margin="0,0,0,-3"
DataContext="{Binding Mode=OneWay, Source={StaticResource local:oPatients}}">
I'm getting StaticResource reference 'local:oPatients' was not found.
Here is the codebehind:
public partial class MainWindow : Window
{
ListBox _activeListBox;
clsPatients oPatients;
public MainWindow()
{
oPatients = new clsPatients(true);
...
To be able to address the object as a StaticResource, it needs to be in a resource dictionary. However, since you're creating the object in MainWindow's constructor, you can set the DataContext in the code-behind like so.
oPatients = new clsPatients(true);
stkWaitingPatients.DataContext = oPatients;
And then change the Binding to this:
{Binding Mode=OneWay}
This is an ok practice if you're not going to be changing the DataContext again, otherwise you'd want a more flexible solution.
Edit: You mentioned ObjectDataProvider in your comment. Here's how you'd do that. First, add an xmlns:sys to the Window for the System namespace (I'm assuming you already have one for xmlns:local):
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Then you can add an ObjectDataProvider to your resource dictionary like this:
<Window.Resources>
<ObjectDataProvider
x:Key="bindingPatients"
ObjectType="{x:Type local:clsPatients}">
<ObjectDataProvider.ConstructorParameters>
<sys:Boolean>True</sys:Boolean>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
</Window.Resources>
And refer to it in a Binding with the StaticResource markup like this, using the same string we specified in the x:Key attached property we gave it in the dictionary:
{Binding Source={StaticResouce bindingPatients}, Mode=OneWay}
Edit 2: Ok, you posted more code in your answer, and now I know why it's throwing an exception during the constructor. You're attempting to do this...
lstWaitingPatients.DataContext = oPatients;
... but lstWaitingPatients doesn't actually exist until after this.InitializeComponent() finishes. InitializeComponent() loads the XAML and does a bunch of other things. Unless you really need to do something before all of that, put custom startup code after the call to InitalizeComponent() or in an event handler for Window's Loaded event.
The following sets the ItemsSource in Code Behind and correctly handles the DataBinding:
public partial class MainWindow : Window
{
public MainWindow()
{
clsPatients oPatients = new clsPatients(true);
//assuming oPatients implements IEnumerable
this.lstWaitingPatients.ItemsSource = oPatients;
And the XAML:
<ListBox x:Name="lstWaitingPatients"
IsSynchronizedWithCurrentItem="true"
ItemTemplate="{StaticResource WaitingPatientsItemTemplate}"
FontSize="21.333" Height="423.291"
ScrollViewer.VerticalScrollBarVisibility="Visible"
GotFocus="lstWaitingPatients_GotFocus"
/>
Now, I can't get this to work...I get a general Windows startup error.
Here is the codebehind with the Initializer and the class being instantiated:
public partial class MainWindow : Window
{
ListBox _activeListBox;
public MainWindow()
{
clsPatients oPatients = new clsPatients(true);
lstWaitingPatients.DataContext = oPatients;
this.InitializeComponent();
Here's the top of my XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Orista_Charting"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
x:Class="Orista_Charting.MainWindow"
x:Name="windowMain"
Title="Orista Chart"
Width="1024" Height="768" Topmost="True" WindowStartupLocation="CenterScreen" Activated="MainWindow_Activated" >
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/ButtonStyles.xaml"/>
<ResourceDictionary Source="Resources/OtherResources.xaml"/>
<ResourceDictionary Source="Resources/TextBlockStyles.xaml"/>
<ResourceDictionary Source="Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
Here's the pertinent XAML, as you see, I went ahead and moved the DataContext down to the ListBox from the StackPanel. This doesn't run, but it does render in Design View (however, with no data present in the ListBox):
<!-- Waiting Patients List -->
<Border BorderThickness="1,1,1,1" BorderBrush="#FF000000" Padding="10,10,10,10"
CornerRadius="10,10,10,10" Background="#FFFFFFFF" Margin="15.245,187.043,0,41.957" HorizontalAlignment="Left" >
<StackPanel x:Name="stkWaitingPatients" Width="300" Margin="0,0,0,-3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Waiting Patients:" VerticalAlignment="Center" FontSize="21.333" Margin="0,0,0,20"/>
<TextBlock HorizontalAlignment="Right" Margin="0,0,38.245,0" Width="139" Height="16"
Text="Minutes Waiting" TextWrapping="Wrap" Foreground="#FF9C2525" FontWeight="Bold" VerticalAlignment="Bottom"
TextAlignment="Right"/>
<!-- Too be implemented, this is the wait animation -->
<!--<Image x:Name="PollGif" Visibility="{Binding Loading}"
HorizontalAlignment="Left" Margin="100,0,0,0" Width="42.5" Height="42.5"
Source="Images/loading-gif-animation.gif" Stretch="Fill"/>-->
</StackPanel>
<ListBox x:Name="lstWaitingPatients"
DataContext="{Binding Mode=OneWay}" ItemsSource="{Binding Mode=OneWay}"
IsSynchronizedWithCurrentItem="true"
ItemTemplate="{StaticResource WaitingPatientsItemTemplate}"
FontSize="21.333" Height="423.291" ScrollViewer.VerticalScrollBarVisibility="Visible"
GotFocus="lstWaitingPatients_GotFocus"
/>
</StackPanel>
</Border>
Ok, but if I just take comment out the assigment line in the codebehind, it does run (albeit with no data in the listbox):
public partial class MainWindow : Window
{
ListBox _activeListBox;
public MainWindow()
{
clsPatients oPatients = new clsPatients(true);
//lstWaitingPatients.DataContext = oPatients;
THANKS!

Resources