ItemsSource vs DataContext in binding case - c#

My main question is about binding case in the scene that we have multiple sources for a control(a combobox inside a datagrid)(or having both datacontext and itemssource). Then how can we know which source the binding will use? (any syntax to clarify that)
Assuming a datagrid has an itemssource="List of Players" datacontext="Manager"
, and has a combobox as a kind of column. We also assume that each player has an Inventory property which is a collection type.
then inside the datagrid.columns:
The current source of each column(for binding) is a Player(this is how i understand it so far). We can only bind to the property of the player not to the property of the datacontext "manager". There is no way to bind to the property of the "Manager". Am i correct?
However, if we move to the combobox columns, then assume i will let combobox's itemssource ='player 's inventory', then the current source for comboboxItem will be each item in the inventory. And if i use the binding, it can only bind to the property of those items.
However, sometimes i see the code that we can also bind to the property of the player inside the combobox's property especially Selected Value and SelectedItem. I am a little confused here
can you help me?
thank you

The key control to think about is an ItemsControl (ComboBox inherits from ItemsControl and the DataGrid behaves very similar).
An ItemsControl has ItemsSource property of type IEnumerable. It also has the ItemTemplate property. What it will do is create one copy of it's ItemTemplate for every item in ItemsSource. The DataContext of the ItemTemplate will be each item in the ItemsSource.
In your case of the ComboBox, the DataContext of the DataGrid's column will be your Player object. If you bind the ComboBox's ItemSource to a Player's inventory, then you will get each item in your ComboBox's list.
The thing to note is that the DataContext of the ComboBox itself is unchanged. It is still the Player object. If you specify an ItemTemplate for your ComboBox, that is what will have it's DataContext to the items in a Player's inventory.

Its really simple.
DataContext refers to the same property of the items. It does not get extended and its not dynamic. DataContext applies to children's properties which are currently inside the parent.
But ItemsSource is dynamic. It gets extended along with the source. Here is a gud example.
This is a sample xaml.
<UserControl x:Class="SilverlightApplication"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<DataTemplate x:Key="template2">
<StackPanel Orientation="Horizontal">
<Image x:Name="img1" Source="{Binding Image}"></Image>
<TextBlock x:Name="data2" Text="{Binding Data}"></TextBlock>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<StackPanel>
<StackPanel x:Name="DataContextStack" Orientation="Vertical">
<TextBlock x:Name="data1" Text="{Binding Text1}"></TextBlock>
<TextBlock x:Name="data2" Text="{Binding Text2}"></TextBlock>
</StackPanel>
<ListBox x:Name="lst2" ItemTemplate="{StaticResource template2}"></ListBox>
</StackPanel>
</Grid>
Here is the code behind.
namespace SilverlightApplication
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
loadLists();
}
private void loadLists()
{
ObservableCollection<Temp2> tColl = new ObservableCollection<Temp2>();
Temp1 t1 = new Temp1();
t1.Text1 = "DataContext1";
t1.Text2 = "DataContext2";
tColl.Add(new Temp2() { Image = "", Data = "Item1" });
tColl.Add(new Temp2() { Image = "", Data = "Item2" });
DataContextStack.DataContext = t1;
lst2.ItemsSource = tColl;
}
}
public class Temp1
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Temp2
{
public string Image { get; set; }
public string Data { get; set; }
}
}
As you can see, the DataContext applies to the Textblocks which exist in the StackPanel and refer to one single property that is Text.
Whereas ItemsSource refers to Source of the Image and Text property of the Textblock and the items inside the list can be extended along with the ObservableCollection.
Or to make it even simpler to you.
DataContext - Value is set based on the design.
ItemsSource - Value is set based on the logic.
Hope this helps.
Mark this as answer, if this answered your question.

Related

Image binding wpf

I'm trying to generate one element that alows me to show some images into a treeview using WPF. I sopose that I have to generate my own TreeViewItem in order to bind the properties I want into the TreeView control. This is my own TreeViewItem:
public class TreeViewItem : System.Windows.Controls.TreeViewItem
{
public ImageSource Image { get; set; }
public TreeViewItem(string text, ImageSource displayedImage)
{
this.Header = text;
this.Image = displayedImage;
}
}
With this object generated, I define the structure of my custom TreeView in order to bind all data:
<UserControl x:Class="test.TreeControl"
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="200" d:DesignWidth="100">
<Grid>
<TreeView Name="TVTree" x:FieldModifier="public">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid Margin="2" MinHeight="25" MaxHeight="25" MinWidth="60">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Image}" Height="16" Width="16"/>
<TextBlock Grid.Column="1" Text="{Binding Header}" Margin="5,0"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
As long as I know, I'm trying to bind the Image Source with the image source of my custom control, and the text of the textblock with the header of my treeviewItem. The problem is that the control doesn't display the image and I find out that the header is not beeing displayed too. It only displays the result of ToString() method (which is the same as the string defined as the Header of the object).
Anybody knows how can I bind this data correctly?
Thanks in advance
When you use data templates, you need to define your data as type e.g. TreeItem and its appearance in a template, here a HierarchicalDataTemplate for a TreeView. A TreeItem could look like this.
public class TreeItem
{
public string Header { get; }
public ImageSource Image { get; }
public ObservableCollection<TreeItem> Children { get; }
public TreeItem(string header, ImageSource displayedImage, ObservableCollection<TreeItem> children)
{
Header = header;
Image = displayedImage;
Children = children;
}
}
You do not inherit from TreeViewItem, because you define data. A TreeViewItem is a container in the view that wraps your data template as an item of the TreeView. In a TreeView there is a hierarchy of items, so you must provide a collection of subitems for any item. This collection can be null or empty if there are no further items.
Then you need a view model for your user control which exposes a collection of TreeItems that you can bind to. I just created a list of dummy data with three nested items in Children collections to show the hierarchy.
public class TreeControlViewModel
{
public ObservableCollection<TreeItem> TreeItems { get; }
public TreeControlViewModel()
{
TreeItems = new ObservableCollection<TreeItem>
{
new TreeItem("Item 1", null, null),
new TreeItem("Item 2", null, null),
new TreeItem("Item 3", null, null),
new TreeItem("Item 4", null, new ObservableCollection<TreeItem>
{
new TreeItem("Item 41", null, null),
new TreeItem("Item 42", null, new ObservableCollection<TreeItem>
{
new TreeItem("Item421", null, null)
})
}),
new TreeItem("Item 5", null, null)
};
}
}
Next you have to create an instance of the TreeControlViewModel and assign it to the TreeControl user control, so you can bind to its TreeItems property, which is the collection for the TreeView.
<UserControl x:Class="test.TreeControl"
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="200"
d:DesignWidth="100">
<UserControl.DataContext>
<local:TreeControlViewModel/>
</UserControl.DataContext>
<!-- ...your tree view XAML code. -->
</UserControl
At last, be sure to actually bind to the TreeItems collection via ItemsSource, otherwise no items are displayed.
<TreeView Name="TVTree" ItemsSource="{Binding TreeItems}">
As a note, ObservableCollection will notify and trigger updating the TreeView when you modify the collection of items with e.g. Add or Remove. If you are interested in modifiying properties like Header, too, you should have a look at INotifyPropertyChanged an how it is implemented to enable updates for them in the user interface, too. For the sake of simplicity and focus on your issue I will skip this part.
Your bindings are looking for an Image and Header in the DataContext, which would be your VM in standard MVVM WPF. If you check your output window in Visual Studio you will see binding errors to that effect.
You do not need your TreeViewItem class, you are already defining the view for a tree item with your template.
You do need a TreeItemVM to bind to. Add your properties in there instead. They should be data only. No visual classes.

How do I update a series of user controls based on a collection of data structures?

I have a user control BookViewControl which contains only a StackPanel, I also have a control named BookInfoControl which contains two TextBlocks in a WrapPanel. In the Codebehind for BookViewControl I have an Observable collection (Books) of a Data Structure (named BookInfo which contains two string properties, one for the author and the other for the books name).
What I want is to set it up in such a way that when I add or remove an item to the observable collection (Books) the Stack Panel in BookViewControl would update by adding or removing an instance of the BookInfoControl (where txtName of BookInfoControl is bound to the corresponding BookInfo's BookName property, and the txtAuthor of BookInfoControl would be bound to the same BookInfos AuthorName property).
Thus my problem is two-fold:
1) How do I dynamically create / remove UserControls based on a collection.
2) How to do I add those controls to another Controls StackPanel.
I know that I can bind an instance of BookInfo and it's properties to an instance of BookInfoControls TextBlocks (either in XAMl with Text={Binding SomeDataMember.Path} or in the code behind with instanceName.SetBinding(new binding{ Source = SomeDataMember, Path = new PropertyPath("Path") }); ). However doing so wouldn't cause new members of my collection Books to create a new instance of BookInfoControl nor would it solve the issue of adding it to the StackPanel.
I also considered putting in a kludge to manage a secondary collection of BookInfoControls and then sync it by hand but this seems too messy and I believe there should be an easier and cleaner way with WPF.
BookViewControl.xaml
<UserControl x:Class="GenericNamespace.BookViewControl "
... Default Generated Namespace Definitions ...
>
<Grid>
<StackPanel x:Name="pnlCollectionOfBookInfos" />
</Grid>
</UserControl>
BookInfoControl.xaml
<UserControl x:Class="GenericNamespace.BookInfoControl "
... Default Generated Namespace Definitions ...
>
<Grid>
<WrapPanel>
<TextBlock x:Name="txtName"/>
<TextBlock x:Name="txtAuthor"/>
</WrapPanel>
</Grid>
</UserControl>
BookInfo.cs
public class BookInfo
{
public string AuthorName { get; set; } = "";
public string BookName { get; set; } = "";
}
BookViewControl.cs
public partial class BookViewControl: UserControl
{
public ObservableCollection<BookInfo> Books { get; set; } = new ObservableCollection<BookInfo>();
public BookViewControl()
{
this.DataContext = this;
InitializeComponent();
}
}
In the end, modifying Books should cause a BookInfoControl to be displayed or removed in the StackPanel of BookViewControl where the text properties of BookInfoControls TextBlocks reflect the properties exposed in BookInfo.
Edit: The following link is something I found following the answer I got here and goes more into depth about this subject, hopefully it'll be of use to the next person who asks this: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview
Welcome to SO!
Whenever you want to render a collection of items in WPF using data binding you have to use an ItemsControl or one of it's derived controls:
<ItemsControl ItemsSource="{Binding Books}" />
If you do this you'll see each book displayed as a string, you change that by declaring a DataTemplate for your Book type:
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:BookInfo}">
<local:BookInfoControl />
</DataTemplate>
</UserControl.Resources>

ListView Observable Collection Will Not Show Members

Cannot get my listview to display data.
XAML
<Grid>
<DockPanel>
<ListView Name="lstDetectedComputers"
MinWidth="200"
DockPanel.Dock="Left"
ItemsSource="{Binding ComputersList}" DisplayMemberPath="ComputerName">
</ListView>
<DataGrid x:Name="ViewNetworkCardInformation"
ItemsSource="{Binding NetworkCardInformation}"/>
</DockPanel>
</Grid>
Code:
private ObservableCollection<Object> _ComputersList;
public ObservableCollection<Object> ComputersList
{
get
{
return _ComputersList;
}
set
{
_ComputersList = value; NotifyPropertyChanged("ComputersList");
}
}
private DataTable _NetworkCardInformation;
public DataTable NetworkCardInformation
{
get
{
return _NetworkCardInformation;
}
set
{
_NetworkCardInformation = value; NotifyPropertyChanged("NetworkCardInformation");
}
}
Strange thing is that NetworkCardInformation shows in my datagrid so this indicates that the datacontext is working as expected.
now im under the impression with a ObservableCollection i do not need a INotifyPropertyChange, if this is wrong please advised.
i have also tried just ItemsSource="{Binding ComputersList}"
I have put a break point into the code to ensure that the observable collection has data, and it is there .
ComputersList Count = 2 System.Collections.ObjectModel.ObservableCollection
[0] {AdminUltimate.Model.NetworkModel.ComputerNode} object {AdminUltimate.Model.NetworkModel.ComputerNode}
ComputerName "ASUS-PC" string
Could someone please assist.
Thank you
You have set DisplayMemberPath as ComputerName but Object doesn't have any such property so it shows nothing on view.
This can be validated by removing DisplayMemberPath, you will see fully qualified class name of your object since ToString() gets called on your object if no ItemTemplate and DisplayMemberPath is set on ListBox.
So, solution would be to change ObservableCollection<Object> to type of more concrete object containing property ComputerName i.e. ObservableCollection<ComputerNode>.

WPF ComboBox Binding To Object On Initial Load

I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.

WPF: Basic question about Dependency Properties

I have the following Xaml in a Window (ArtistInfo):
<Grid>
<TextBlock Text="{Binding Artist.Name}"></TextBlock>
</Grid>
And this is the code-behind for the same window (code simplified for question's sake):
public static readonly DependencyProperty ArtistProperty =
DependencyProperty.Register("Artist", typeof(Artist), typeof(ArtistInfo));
Artist Artist {
get {
return (Artist)GetValue(ArtistProperty);
}
set {
SetValue(ArtistProperty, value);
}
}
public ArtistInfo() {
InitializeComponent();
}
public ArtistInfo(int artistID) {
InitializeComponent();
Artist = GetArtist(artistID);
}
Basically what I'm trying to do is data binding to a Dependency Property, so that when Artist is populated (in the constructor), the TextBlock gets filled with the Artist's name.
What am I missing here?
The only thing I didn't see was you updating the Binding source for the TextBlock. First add a name to the TextBlock
<TextBlock Name="m_tb" ... />
Then update the DataContext value in the constructor
public ArtistInfo() {
...
m_tb.DataContext = this;
}
EDIT OP mentioned that there may be more than one TextBlock or child element.
In that case I would do the above trick for the closest parent object to all of the values. In this case the Grid control. The DataContext property will be inherited so to speak by all of the inner children.
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}" ...>
<Grid>
<TextBlock Text="{Binding Artist.Name}"/>
</Grid>
</Window>

Categories

Resources