I have a ViewModel named "A", containing a property of type ObservableCollection(Of ChannelViewModel). The Model is actually inside this ChannelViewModel class and I'm ok with that.
In the View "A", I have a stackpanel with ItemsSource filled with the ObservableCollection. I added a DataTemplate to show a custom control "Channel" instead of the ChannelViewModel string. The Channel custom control needs to display ChannelViewModel data.
The problem is the DataContext of the Channel is not connected properly to each items of the list. I tried a bunch of things and nothing seems to work. I would appreciate some help please!
Here is the "A" View code:
<Window.DataContext>
<ctrls:AViewModel/>
</Window.DataContext>
<StackPanel x:Name="uiStack" Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Channels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:Channel DataContext="{Binding DataContext}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
Here is "A" ViewModel :
Private aChannels As New ObservableCollection(Of ChannelViewModel)
Property Channels As ObservableCollection(Of ChannelViewModel)
Get
Return aChannels
End Get
Set(value As ObservableCollection(Of ChannelViewModel))
aChannels = value
OnPropertyChanged("Channels")
End Set
End Property
Public Sub New()
AddChannels()
OnPropertyChanged("Channels")
End Sub
Private Sub AddChannels()
For i As Integer = 1 To DeviceConfig.Channels.Count
Channels.Add(New ChannelViewModel(i))
Next
End Sub
The Channel UserControl is (simplified):
<UserControl.DataContext>
<ctrls:ChannelViewModel />
</UserControl.DataContext>
<StackPanel Orientation="Horizontal">
<Label x:Name="lblChannelNo" Content="{Binding ChannelNo}" />
<Label x:Name="lblChannelName" Content="{Binding ChannelName}" />
</StackPanel>
And Channel ViewModel (simplified):
Public Class ChannelViewModel
Inherits ViewModelBase
Private aChannelNo As Integer = 0
Property ChannelNo As Integer
Get
Return aChannelNo
End Get
Set(value As Integer)
aChannelNo = value
OnPropertyChanged("ChannelNo")
End Set
End Property
Private aChannelName As String = "N/A"
Property ChannelName As String
Get
Return aChannelName
End Get
Set(value As String)
aChannelName = value
OnPropertyChanged("ChannelName")
End Set
End Property
Okay, the problem was in the Channel View. I think setting the DataContext was overwriting the context from the "A" View. So removing these lines solved the issue:
<UserControl.DataContext>
<ctrls:ChannelViewModel />
</UserControl.DataContext>
I also had to change the binding to :
<DataTemplate>
<ctrls:Channel DataContext="{Binding}" />
</DataTemplate>
Related
Im new to WPF and VB and im having some trouble figuring out why the thing i select in a ComboBox dropdown does not show in the ComboBox after selection.
I have my ComboBox populated through bindings and DataContext. This is my Settings.xaml file
<Window.Resources>
<DataTemplate x:Key="TabList">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Header}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
...
<ComboBox x:Name="tabs"
ItemsSource="{Binding tabList}"
ItemTemplate="{StaticResource ResourceKey=TabList}"
Height="32" />
The views codebehind file (Settings.xaml.vb) then loads the ComboBoxes content in the class' constructor, and the data does show in the ComboBox
Public Class Settings
Private loader As SettingsLoader
Sub New()
InitializeComponent()
Dim sh As New SettingsHandler(True)
loader = New SettingsLoader
loader.tabList = sh.Current.Tabs
DataContext = loader
End Sub
End Class
The SettingsLoader class looks like so. TRTab is my own class that simply inherits from TabItem and only adds a few extra properties, nothing fancy
Public Class SettingsLoader
Private _tabs As List(Of TRTab)
Public Property tabList() As List(Of TRTab)
Get
Return _tabs
End Get
Set(value As List(Of TRTab))
_tabs = value
End Set
End Property
End Class
Do i need to add a property to my SettingsLoader that holds the selected item for the ComboBox to show or what am i missing ?
EDIT: Just to clarify what im trying to achieve: I have a TabControl with a number of tabs. Those tabs' Headers needs to be also shown in a ComboBox for selection
Because TabItem is a ContentControl the ComboBox will display its Content when the item is selected. You could confirm this yourself using the following XAML markup:
<Window.Resources>
<DataTemplate x:Key="TabList">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Header}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ComboBox x:Name="tabs" Height="32"
ItemTemplate="{StaticResource TabList}">
<TabItem Content="Content" Header="Header" />
</ComboBox>
</StackPanel>
When you open the dropdown, you will see "Header" but when you select the item and close the dropdown you will see "Content".
It generally doesn't make a whole much sense to set the ItemsSource property of a ComboBox to an IEnumerable of ContentControls. You could bind the ItemsSource to an IEnumerable(Of String) instead. Just add another property to your SettingsLoader class:
Public Class SettingsLoader
Private _tabs As List(Of TRTab)
Public Property tabList() As List(Of TRTab)
Get
Return _tabs
End Get
Set(value As List(Of TRTab))
_tabs = value
End Set
End Property
Public ReadOnly Property tabHeaders() As IEnumerable(Of String)
Get
If _tabs Is Nothing Then
Return Nothing
End If
Return _tabs.Select(Function(x) x.Header.ToString())
End Get
End Property
End Class
<Window.Resources>
<DataTemplate x:Key="TabList">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ComboBox x:Name="tabs"
ItemsSource="{Binding tabHeaders}"
ItemTemplate="{StaticResource ResourceKey=TabList}"
Height="32" />
</StackPanel>
The other option is to set the Content properties of your TRTab objects to the same values ass their Header properties.
The instance of the class is a private member of the view code exposed as a public property named "ViewModel".
You are setting the DataContext of the Grid to a string equal to "ViewModel". You need to make sure the DataContext property is correctly set to actual ViewModel object instance, either with a binding or via code behind.
For more information, see my answer to the question What is DataContext for?
I'm agree with the Rachel's answer. An easy way to set the DataContext of your Grid could be this:
<Window.Resources>
<YourNamespace:ViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<TextBox Text="{Binding Path=TestName}" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="TextBox1" VerticalAlignment="Top" Width="479" />
</Grid>
This way you don't need to touch the code behind of your Window/UserControl.
If you don't want to change the code in your view and and want to keep your ViewModel property, then you could also do this:
Public Class View Inherits Window
Private m_ViewModel As ViewModel
Public Property ViewModel() As ViewModel
Get
Return m_ViewModel
End Get
Set
m_ViewModel = Value
End Set
End Property
Public Sub New()
InitializeComponent()
ViewModel = New ViewModel()
DataContext = ViewModel
End Sub
End Class
So you don't need to set the DataContext in your view, just do this:
<Grid>
<TextBox Text="{Binding Path=TestName}" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="TextBox1" VerticalAlignment="Top" Width="479" />
</Grid>
I built a multicolumn combobox in WPF following this SO post: WPF ComboBox Multiple Columns
(Please excuse the mass of code samples ;))
The Combobox XAML
<ComboBox Name="cmbProductTypeMulti" IsEditable="False" Margin="0,2,10,2" MaxDropDownHeight="250"
Text="{Binding Path=AcctData.ProductType}" ItemsSource="{Binding Path=ProductTypeSelection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="2" Text="{Binding Path=ProductType}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem" BasedOn="{StaticResource ComboBoxItemStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Name="ComboBoxItemBorder" BorderThickness="1">
<Grid Name="ComboBoxItemGrid" TextElement.Foreground="Black">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5,3" Grid.Column="0" Text="{Binding ProductType}"/>
<TextBlock Margin="5,3" Grid.Column="1" Text="{Binding Description}" FontSize="10"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!--- snip --->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
ViewModel snippet
Public Class AccountsViewModel
Inherits ViewModelBase
//The view model's main Poco
Dim _AcctData As Models.Account
//The multicolumn combobox's Poco
Dim _ProductTypeSelection As IEnumerable(Of Models.ProductType)
Public Property AcctData As Account
Get
Return _AcctData
End Get
Set(value As Account)
MyBase.Set(Of Account)(Function() AcctData, _AcctData, value)
End Set
End Property
Public Property ProductTypeSelection As IEnumerable(Of ProductType)
Get
Return _ProductTypeSelection
End Get
Set(value As IEnumerable(Of ProductType))
MyBase.Set(Of IEnumerable(Of ProductType))(Function() ProductTypeSelection, _ProductTypeSelection, value)
End Set
End Property
...
End Class
View's Poco snippet
Public Class Account
Inherits ObservableObject
Private _ProductType As String
Public Property ProductType As String
Get
Return _ProductType
End Get
Set(value As String)
MyBase.Set(Of String)(Function() ProductType, _ProductType, value)
End Set
End Property
...
End Class
Combobox's Poco
Public Class ProductType
Inherits ObservableObject
Private _ProductType As String
Private _Description As String
Public Property ProductType As String
...
End Property
Public Property Description As String
...
End Property
...
End Class
The Problem
First off, the when I load an account into the view model's AcctData.ProductType, the value is not displayed in the multicolumn combobox. I have a regular combobox bound to the same value which displays AcctData.ProductType initial value properly.
Secondly, when I select an item from the multicolumn combobox, the regular combobox loses its selected item and goes blank. When I go into debug and look at the vm's AcctData.ProductType, I find that it has been assigned the ToString value of the ProductType poco.
So it looks like the multicolumn combobox is trying to use the whole Poco to bind with. How can I get it use a property from a Poco as its binding value?
Thanks
The first problem would probably be solved if you would call the ProductTypeSelection setter from the viewmodel's constructor to initialize the data, because currently it doesn't get set en thus won't raise the propertychanged event, currently your binding only knows about the initial data which is the default null
the second problem is probably caused because the xaml is taken the datatemplate over the controltemplate you have defined and would probably be solved if you put the xaml thats inside the controltemplate inside of the datatemplate
I managed to find a hacky work around.
Since the multicolumn combobox is binding using the Poco's ToString() value, I simply modified ToString() to return the ProductType property, which is the property that I want to be bound! Now the multicolumn combobox behaves as it should - even though its technically binding to the wrong property.
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>
``
my binded treeview work fine with string property but not with a "property of another property".
My code:
Public class A
Public data as string
End Class
Public Class T
Public o As A
Public ReadOnly Property desc As String
Get
Return o.data
End Get
End Property
Property children As New ObservableCollection(Of T)()
End Class
xaml that work:
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=children}" >
<TextBlock Text="{Binding Path=desc}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
xaml that not work:
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=children}" >
<TextBlock Text="{Binding Path=o.data}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
What is the mistake?
thanks.
You can only bind to properties in silverlight. Your o is not a property at the moment, you need to define setters and getters.
I'm not familiar with vb so I'm not sure what the correct syntax is. Either define it as a property or define set and get for o.