WPF binding automatically to the first collection item - wpf

Is it the intended behavior that a binding to a collection automatically uses the first item as source?
Example Xaml:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock Text="{Binding ColContent}" />
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
and Code:
using System.Collections.Generic;
using System.Windows;
namespace ListSelection
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyCol("col 1")
{
new MyItem("item 1"),
new MyItem("item 2")
};
}
}
public class MyItem
{
public string ItemContent { get; set; }
public MyItem(string content)
{
ItemContent = content;
}
}
public class MyCol : List<MyItem>
{
public string ColContent { get; set; }
public MyCol(string content)
{
ColContent = content;
}
}
}
The UI shows up with:
col 1
item 1
The second binding took implicitly the first collection item as source! So bug, feature or intended?
EDIT: .net 4.5, VS2012, corrections
EDIT 2:
I further investigated the problem together with a mate and got closer to the solution:
<Window x:Class="ListSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemContent}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Text="{Binding ItemContent}" />
</StackPanel>
</Window>
The - lets call it - magic binding seems to exist for master detail views. By default any collection that is bound gets a CollectionView - which provides a selected item property (and other cool stuff like sorting). This selected item can be used shortcutted for the detailed view. If the IsSynchronizedWithCurrentItem is set to true the shortcutted binding reacts to changed selections. The problem in the whole thing: the selected item of the CollectionView is alway set to the first item which leads to the magic binding... I would call that a bug and it should only work explicitly, e.g. by binding the collection to a Selector with the IsSynchronizedWithCurrentItem set.

Related

How to show the selected item of a WPF binding

C#:
public void SetCompetition(Window wT1)
{
//Add all the Copetition
wT1._competition = new List<Competition>();
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test1", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test2", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test3", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test4", IsSelected = false });
wT1.cboSetupCompetition.ItemsSource = wT1._competition;
wT1.cboSetupCompetition.Items.Refresh();
}
Data Template:
<UserControl.Resources>
<System:Double x:Key="Double1">11</System:Double>
<DataTemplate x:Key="cmbCompetition">
<WrapPanel Height="30" >
<Label Content="{Binding Name}" ></Label>
</WrapPanel>
</DataTemplate>
</UserControl.Resources>
<ComboBox x:Name="cboSetupCompetition" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" IsEditable="True" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
I have a Combobox with a label and an image and when I select an item I would like to see the same format in the Combobox when it is closed. I am not getting any errors I am seeing the name of the application.Competition(this is my object Model) instead of the values of the image and label.
The SetCopetition is invoked when the application loads.
A TextBox is not able to display a Label and an Image or whatever elements that are in your DataTemplate in it.
Set the IsEditable property of the ComboBox to false and it should work as expected, i.e. your DataTemplate will be applied to the selected item when the ComboBox is closed:
<ComboBox x:Name="cboSetupCompetition" IsEditable="False" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
Your issue has nothing to do with MVVM...
the specific problem as Mn8 spotted is that IsEditable=true forces the combo to display a textbox as the selected item
However you are still thinking winforms not WPF, using code behind to link data into the view causes many problems and instability as quite often this breaks the binding connections which is what is suspected was your problem initially, using a proper MVVM approach will eliminate all these problems
the best overveiw of MVVM i know of is
https://msdn.microsoft.com/en-gb/library/hh848246.aspx
Model
this is your data layer, it handle storage and access to data, your model will handle access to files, databases, services, etc
a simple model would be
public class Model
{
public string Text { get; set; }
public Uri Uri { get; set; }
}
ViewModel
on top of your Model you have your View Model
this manages the interaction of your View with the model
for example here because it uses Prism's BindableBase the SetProperty method notifies the View of any changes to the data, the ObservableCollection automatically notifies of changes to the collection, it also uses Prism's DelegateCommand to allow method binding in the view
public class ViewModel:BindableBase
{
public ViewModel()
{
AddItem = new DelegateCommand(() => Collection.Add(new Model()
{
Text = NewText,
Uri = new Uri(NewUri)
}));
}
private string _NewText;
public string NewText
{
get { return _NewText; }
set { SetProperty(ref _NewText, value); }
}
private string _NewUri;
public string NewUri
{
get { return _NewUri; }
set { SetProperty(ref _NewUri, value); }
}
private Model _SelectedItem;
public Model SelectedItem
{
get { return _SelectedItem; }
set
{
if (SetProperty(ref _SelectedItem, value))
{
NewText = value?.Text;
NewUri = value?.Uri.ToString();
}
}
}
public ObservableCollection<Model> Collection { get; } = new ObservableCollection<Model>();
public DelegateCommand AddItem { get; set; }
}
View
the View ideally does nothing but displays and collects data, all formatting / Styling should be done here
firstly you need to define the data source, the usual way is via the data context as this auto inherits down the visual tree, in the example because i set the window's datacontext, i have also set it for everything in the window the only exception is the dataTempplate as this is set to the current item in the collection
i then bind properties to the datasource
Note the code behind file is only the default constructor no other code at all
<Window
x:Class="WpfApplication1.MainWindow"
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:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<GroupBox Header="Text">
<TextBox Text="{Binding NewText}"/>
</GroupBox>
<GroupBox Header="URI">
<TextBox Text="{Binding NewUri}"/>
</GroupBox>
<Button Content="Add" Command="{Binding AddItem}"/>
<ComboBox ItemsSource="{Binding Collection}" SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Uri}" />
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>

WPF Specifying HierarchicalDataTemplate for Interface

I've found a really strange quirk in WPF. If I specify a DataTemplate for an interface, it will work if defined inside an ItemsControl.ItemTemplate, but will not work if defined inside ItemsControl.Resrouces.
Concrete example:
I have a tree structure I want to represent. All items in the tree implement IHardware, but they do not necessarily have a common base type. If I define a HierarchicalDataTemplate for IHardware inside TreeView.ItemTemplate, everything works swimmingly. If I define the template inside TreeView.Resources, it never gets used/applied. The following shows the same data in 2 columns, the first column works as expected, the second column does not.
<Window x:Class="WPFInterfaceBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self ="clr-namespace:WPFInterfaceBinding"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Works -->
<Border
Grid.Column="0"
Background="Gray">
<TreeView
ItemsSource="{Binding Hardware}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type self:IHardware}"
ItemsSource="{Binding SubHardware}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
<!-- Doesn't work -->
<Border
Grid.Column="1"
Background="Gray">
<TreeView
ItemsSource="{Binding Hardware}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type self:IHardware}"
ItemsSource="{Binding SubHardware}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Border>
</Grid>
</Window>
Note that in the second column, nothing has changed except TreeView.ItemTemplate -> TreeView.Resources
Why is this the case? How can I get the template to work when inside Resources? I imagine I can work around this using a DataTemplateSelector, but first I'm curious if there's a way to actually get it working as expected.
Code behind, for completeness
using System.Windows;
namespace WPFInterfaceBinding
{
public partial class MainWindow : Window
{
public IHardware[] Hardware { get; private set; }
public MainWindow ()
{
Hardware = InitializeHardware();
InitializeComponent();
}
private IHardware[] InitializeHardware ()
{
return new Hardware[] {
new Hardware("Component 1", new Hardware[] {
new Hardware("Sub Component 1"),
new Hardware("Sub Component 2")
}),
new Hardware("Component 2", new Hardware[] {
new Hardware("Sub Component 3"),
new Hardware("Sub Component 4")
})
};
}
}
public class Hardware : IHardware
{
public string Name { get; set; }
public IHardware[] SubHardware { get; set; }
public Hardware ( string name, Hardware[] subHardware = null )
{
Name = name;
SubHardware = subHardware ?? new Hardware[0];
}
}
public interface IHardware
{
string Name { get; set; }
IHardware[] SubHardware { get; set; }
}
}
Additional information:
I can't simply use ItemTemplate because in my actual usage scenario there will be non-IHardware items mixed in using a CompositeCollection so I need multiple templates.
I can't change the types of the collections from IHardware to something concrete because I'm displaying data from code I don't control.
This is just example code, not representative of any design patterns actually in use.
Defining the template inside TreeView.Resources works just fine if the type is changed from IHardware to Hardware.
Turns out, WPF just doesn't like binding to interfaces. The only work around I could figure out was to use a DataTemplateSelector.
public class OHMTreeTemplateSelector : DataTemplateSelector
{
public HierarchicalDataTemplate HardwareTemplate { get; set; }
public DataTemplate SensorTemplate { get; set; }
public override DataTemplate SelectTemplate ( object item, DependencyObject container )
{
if ( item is IHardware ) return HardwareTemplate;
else if ( item is ISensor ) return SensorTemplate;
return base.SelectTemplate(item, container);
}
}
Though, for other reasons I ended up creating a separate ViewModel for the data that exposes it through concrete types, circumventing this issue.

Caliburn.Micro and TabControl

I'm trying to bind a ObservableCollection of viewmodels to a TabControl.
In my MainViewModel I have : (INotifyPropertyChanged is handled through Fody.PropertyChanged)
public ObservableCollection<ImageManagementViewModel> ImageManagement { get; set; }
And my MainView :
<TabControl
Margin="5"
x:Name="ImageManagement" />
My ImageManagementViewModel implements IHaveDisplayName, as the documentation seems to indicate is needed.
If the TabControl’s DisplayMemberPath is not set and the ViewModel implements IHaveDisplayName, then we set it’s ItemTemplate to the DefaultHeaderTemplate, which looks like this: [...]
/// <summary>
/// Gets the display name
/// </summary>
public string DisplayName
{
get
{
return string.Format("{0}x{1}", this.X, this.Y);
}
set
{
throw new NotImplementedException();
}
}
But my tab's titles are always "TestApplication.ViewModels.ImageManagementViewModel", instead of the DisplayName.
If I implement the DisplayName "by hand" it works, but I'd like it to be automatic, because why doesn't it work ?
<TabControl
Margin="5"
x:Name="ImageManagement">
<TabControl.ItemTemplate>
<ItemContainerTemplate>
<TextBlock Text="{Binding DisplayName}" />
</ItemContainerTemplate>
</TabControl.ItemTemplate>
</TabControl>
What am I missing ?

How to use CollectionViewSource with design time data in Expression Blend?

I wonder how I can show design time data in Expression Blend that is located inside a SampleData.xaml using a CollectionViewSource? Before changing my code to use the CVS, I used an ObservableCollection. I was in the need to filter and sort the items inside there, thus I changed the code to use the CVS. Now my designer complains about not being able to fill the SampleData's NextItems with a proper structure to show up in Expression Blend. Here is some code I use inside the app:
MainViewModel.cs
class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
NextItems = new CollectionViewSource();
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
some functions to fill, filter, sort etc...
}
MainView.xaml:
<phone:PhoneApplicationPage
... some other stuff ...
d:DesignWidth="480"
d:DesignHeight="728"
d:DataContext="{d:DesignData SampleData/SampleData.xaml}">
<Grid
x:Name="LayoutRoot"
Background="Transparent">
<controls:Panorama>
<controls:PanoramaItem>
<ListBox ItemsSource="{Binding NextItems.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Image}" />
<StackPanel>
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
</controls:Panorama>
</Grid>
</phone:PhoneApplicationPage>
SampleData.xaml
<local:MainViewModel
xmlns:local="clr-namespace:MyAppNamespace"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:swd="clr-namespace:System.Windows.Data;assembly=System.Windows" >
<local:MainViewModel.AllItems>
<local:ItemModel
FullName="Dummy"
Image="/Images/dummy.png" />
</local:MainViewModel.AllItems>
<local:MainViewModel.NextItems>
How to fill the CollectionViewSource's Source?
</local:MainViewModel.NextItems>
</local:MainViewModel>
So the question I can't find an answer to is how to fill the Source for NextItems in SampleDate.xaml? Any help would be much appreciated.
if you want to show sample data in the designer I would recommend you to do it from code. There are two ways of generating sample data for the Blend Designer or the VStudio designer:
From an XML file as you do.
From a c# class -> Best option
best option.
In WPF, in windows 8 and in WP7.5 and highger, you can access a propertie called:Windows.ApplicationModel.DesignMode.DesignModeEnabled making use of it you can seed your ObservableCollection from your view model:
public class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
if (DesignMode.DesignModeEnabled)
{
AllItems = FakeDataProvider.FakeDataItems;
}
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
}
In this way, if you change the model, you dont' have to regenerate an XML file, it's a little bit cleaner from a C# file. The FakeDataProvider is an static class where all design-time fake data are stored. So in you XAML the only thing you have to do is to bind your Listbox to the collection of your ViewModel.

Why does this simple databinding scenario not work? (ComboBox related)

I've been scratching my head on this one for a while now and am stumped at the moment.
The problem scenario is easier to explain as code so hopefully it speaks for itself. First of all, I have a silverlight application with the following in the XAML...
<UserControl x:Class="SilverlightApplication2.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<UserControl.Resources>
<DataTemplate x:Key="icTemplate">
<ComboBox ItemsSource="{Binding StringsChild}" SelectedItem="{Binding SelectedItem}"/>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl x:Name="ic" ItemTemplate="{StaticResource icTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button Click="Save" Grid.Row="1" Content="GO"/>
</Grid>
My code-behind looks like this...(all written in a single class file so that it's easy for you to copy it into your own project and compile)
namespace SilverlightApplication2
{
public partial class Page : UserControl
{
public ObservableCollection<SomeClass> StringsParent { get; set; }
public Page()
{
InitializeComponent();
StringsParent = new ObservableCollection<SomeClass>();
ic.ItemsSource = StringsParent;
}
private void Save(object sender, RoutedEventArgs e)
{
SomeClass c = new SomeClass();
c.StringsChild.Add("First");
c.StringsChild.Add("Second");
c.StringsChild.SetSelectedItem("Second");
StringsParent.Add(c);
}
}
public class SomeClass
{
public SelectableObservablecollection<string> StringsChild { get; set; }
public SomeClass()
{
StringsChild = new SelectableObservablecollection<string>();
}
}
public class SelectableObservablecollection<T> : ObservableCollection<T>
{
public SelectableObservablecollection()
: base()
{
}
public void SetSelectedItem<Q>(Q selectedItem)
{
foreach (T item in this)
{
if (item.Equals(selectedItem))
{
SelectedItem = item;
return;
}
}
}
private T _selectedItem;
public T SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedItem"));
}
}
}
}
So let me explain...
I set out to write a generic way of creating an ObservableCollection that has a SelectedItem property on it so that when I bind the collection to a ComboBox for example, I can Bind the ComboBox's SelectedItem property to it.
However, for some reason, it does not seem to work when the ComboBox is effectively nested via an ItemTemplate. I effectively have a list of lists, a scenario which is simple enough that I'm lost as to what's wrong.
When you run the code you'll see that the templated ComboBox does pick up the correct items, but it's never set to a SelectedItem despite the binding.
I know it's rather long winded, but...any ideas?
Thanks alot
The debugger output actually gives you a hint to the problem:
System.Windows.Data Error: BindingExpression path error: 'SelectedItem' property not found on 'ExpressionElements.SomeClass' 'ExpressionElements.SomeClass' (HashCode=49044892). BindingExpression: Path='SelectedItem' DataItem='ExpressionElements.SomeClass' (HashCode=49044892); target element is 'System.Windows.Controls.ComboBox' (Name=''); target property is 'SelectedItem' (type 'System.Object')..
Because the Data context for the template is an instance of the SomeClass class, all you have to do is change the SelectedItem binding from SelectedItem to StringsChild.SelectedItem:
<DataTemplate x:Key="icTemplate">
<ComboBox ItemsSource="{Binding StringsChild}"
SelectedItem="{Binding StringsChild.SelectedItem}"/>
</DataTemplate>

Resources