I have a ItemsControl with a WrapPanel as ItemsHost and multiple Groupings.
Got this going so far with the following Templates:
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" x:Name="PART_Header" Content="{TemplateBinding Content}" />
<ItemsPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Vertical" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
Now I have the Problem that every Group does start a new Column why I want it to Continue right under the last GroupItem and Wrap in the Middle of the GroupItem instead of at the beginning.
It should look like the Windows 8 Apps overview (not the start page, if you go down to the overview)
Is that possible?
I solved this in the ViewModel instead. I add a GroupItem into the ObservableCollection that is styled like a (Expandable) GroupHeader.
Than I added a seperate DataTemplate for the GroupHeader that sets a IsCollapsed property on the Group. All Items do now have a reference to the parent Group and bind the Visibility to the IsCollapsed property of the Parent Group.
Sadly i was not able to achive this using the CollectionViewSource.
This is the XAML:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.DataContext>
<local:ViewModel/>
</ItemsControl.DataContext>
<ItemsControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<DataTemplate DataType="{x:Type local:GroupViewModel}">
<StackPanel>
<CheckBox IsChecked="{Binding IsExtended}" />
<!--Restyle to look like a extender-->
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<TextBlock Text="{Binding Name}"
Visibility="{Binding Group.IsExtended, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
This is the ViewModel:
public class ViewModel
{
public ViewModel()
{
//Some Test data
GroupViewModel group1 = new GroupViewModel("Group1");
GroupViewModel group2 = new GroupViewModel("Group2");
this.Items = new ObservableCollection<object>(new[]
{
new ItemViewModel("Item1", group1),
new ItemViewModel("Item2", group1),
new ItemViewModel("Item3", group2),
new ItemViewModel("Item4", group2)
});
string groupName = string.Empty;
foreach (ItemViewModel item in this.Items.ToArray())
{
//Insert Group headers
if (item.Group.Name != groupName)
{
groupName = item.Group.Name;
this.Items.Insert(this.Items.IndexOf(item), item.Group);
}
}
}
public ObservableCollection<object> Items { get; }
}
public class GroupViewModel : ViewModelBase
{
private bool isExtended = true;
public GroupViewModel(string name)
{
this.Name = name;
}
public string Name { get; }
public bool IsExtended
{
get { return this.isExtended; }
set { this.SetProperty(ref this.isExtended, value); }
}
}
public class ItemViewModel
{
public ItemViewModel(string name, GroupViewModel group)
{
this.Name = name;
this.Group = group;
}
public string Name { get; }
public GroupViewModel Group { get; }
}
You can't do that kind of wrapping directly, since groups are in one panel and items in the each group in their own panel.
You have two options:
Use combination of viewmodel and Datatemplates to simulate grouping. ItemsSource won't be grouped CollectionView, just plain collection, but the items will contain group name. In DataTemplate you will show Group Header for the first item in each group.
Create your own control, which will arrange children into groups out of the box. Quite a lof of work, but much better reusability. I believe this is how WinRT GridView and ListView grouping works. Maybe you can find simmilar 3rd party controls in WPF
Related
Okay, I got this Tabcontrol containing a ListBox. Now my problem it that I would like to bind <TextBox x:Name="DetailTextBox" Text="{Binding Detail}"/> to the selectedItem in the listbox and show the Detail property value.
Note that the TextBox is not part of the TabControl, but is in another Column.
I can't quite figure out, how to handle binding, when there a multiple ListBox'es, one in each TabControl Item.
My classes
public class TabViewModel
{
public string Name { get; set; }
public ObservableCollection<TabItemViewModel> Collection { get; set; }
}
public class TabItemViewModel
{
public string Title { get; set; }
public string Detail { get; set; }
}
public MainWindow()
var tabViewModels = new ObservableCollection<TabViewModel>();
tabViewModels.Add(new TabViewModel{Name = "Tab 1", Collection = new ObservableCollection<TabItemViewModel>{new TabItemViewModel{Detail = "Detail 1.1", Title = "Title 1.1"}, new TabItemViewModel{Detail = "Detail 2.2", Title = "Title 2.2"}}});
tabViewModels.Add(new TabViewModel { Name = "Tab 2", Collection = new ObservableCollection<TabItemViewModel> { new TabItemViewModel { Detail = "Detail 2.1", Title = "Title 2.1" }, new TabItemViewModel { Detail = "Detail 2.2", Title = "Title 2.2" } } });
tabViewModels.Add(new TabViewModel { Name = "Tab 3", Collection = new ObservableCollection<TabItemViewModel> { new TabItemViewModel { Detail = "Detail 3.1", Title = "Title 3.1" }, new TabItemViewModel { Detail = "Detail 3.2", Title = "Title 3.2" } } });
DataContext = tabViewModels;
MainWindow.xaml.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<TabControl ItemsSource="{Binding}" Grid.Column="0" SelectedIndex="0">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header">
<Setter.Value>
<Binding Path="Name"/>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<StackPanel Grid.Column="1">
<TextBox x:Name="DetailTextBox" Text="{Binding Detail}"/>
</StackPanel>
</Grid>
EDIT
Temp Solution
Found a way to make it work, but I'm still looking for a pure Xaml solution.
Added a SelectionChange event
<ListBox ItemsSource="{Binding Collection}" SelectionChanged="ListBox_SelectionChanged">
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
DetailTextBox.DataContext = (TabItemViewModel) e.AddedItems[0];
}
How about this, I was surprised myself :-)
Make these changes to your Xaml.
<TabControl ItemsSource="{Binding}"
Grid.Column="0" SelectedIndex="0"
IsSynchronizedWithCurrentItem="True">
<ListBox ItemsSource="{Binding Collection}"
IsSynchronizedWithCurrentItem="True">
<TextBox x:Name="DetailTextBox"
Text="{Binding /Collection/Detail}"/>
The '/' binds to the currently selected item of a control's CollectionView.
So the binding above is drilling down through
The currently SelectedItem of the ObservableCollection set on the Data Context
The Collection property on that item
The currently SelectedItem of the Collection property (ObservableCollection)
The Detail property on that item.
In order for this to work we need to specify IsSynchronizedWithCurrentItem="True" to ensure the SelectedItem remains synchronized with the current item of each collection.
how can I bind the Content of a ContentControl to an ObservableCollection.
The control should show an object as content only if the ObservableColelction contains exactly one object (the object to be shown).
Thanks,
Walter
This is easy. Just use this DataTemplate:
<DataTemplate x:Key="ShowItemIfExactlyOneItem">
<ItemsControl x:Name="ic">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><Grid/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="ic" Property="ItemsSource" Value="{Binding}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
This is used as the ContentTemplate of your ContentControl. For example:
<Button Content="{Binding observableCollection}"
ContentTemplate="{StaticResource ShowItemIfExactlyOneItem}" />
That's all you need to do.
How it works: The template normally contains an ItemsControl with no items, which is invisible and has no size. But if the ObservableCollection that is set as Content ever has exactly one item in it (Count==1), the trigger fires and sets the ItemsSource of the ItmesControl, causing the single item to display using a Grid for a panel. The Grid template is required because the default panel (StackPanel) does not allow its content to expand to fill the available space.
Note: If you also want to specify a DataTemplate for the item itself rather than using the default template, set the "ItemTemplate" property of the ItemsControl.
+1, Good question :)
You can bind the ContentControl to an ObservableCollection<T> and WPF is smart enough to know that you are only interested in rendering one item from the collection (the 'current' item)
(Aside: this is the basis of master-detail collections in WPF, bind an ItemsControl and a ContentControl to the same collection, and set the IsSynchronizedWithCurrentItem=True on the ItemsControl)
Your question, though, asks how to render the content only if the collection contains a single item... for this, we need to utilize the fact that ObservableCollection<T> contains a public Count property, and some judicious use of DataTriggers...
Try this...
First, here's my trivial Model object, 'Customer'
public class Customer
{
public string Name { get; set; }
}
Now, a ViewModel that exposes a collection of these objects...
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
// Add and remove items to check that the DataTrigger fires correctly...
MyCollection.Add(new Customer { Name = "John Smith" });
//MyCollection.Add(new Customer { Name = "Mary Smith" });
}
public ObservableCollection<Customer> MyCollection { get; private set; }
}
Set the DataContext in the Window to be an instance of the VM...
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
and here's the fun bit: the XAML to template a Customer object, and set a DataTrigger to remove the 'Invalid Count' part if (and only if) the Count is equal to 1.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate x:Name="template">
<Grid>
<Grid Background="AliceBlue">
<TextBlock Text="{Binding Name}" />
</Grid>
<Grid x:Name="invalidCountGrid" Background="LightGray" Visibility="Visible">
<TextBlock
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="Invalid Count" />
</Grid>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="invalidCountGrid" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ContentControl
Margin="30"
Content="{Binding MyCollection}" />
</Window>
UPDATE
To get this dynamic behaviour working, there is another class that will help us... the CollectionViewSource
Update your VM to expose an ICollectionView, like:
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
CollectionView = CollectionViewSource.GetDefaultView(MyCollection);
}
public ObservableCollection<Customer> MyCollection { get; private set; }
public ICollectionView CollectionView { get; private set; }
internal void Add(Customer customer)
{
MyCollection.Add(customer);
CollectionView.MoveCurrentTo(customer);
}
}
And in the Window wire a button Click event up to the new 'Add' method (You can use Commanding if you prefer, this is just as effective for now)
private void Button_Click(object sender, RoutedEventArgs e)
{
_viewModel.Add(new Customer { Name = "John Smith" });
}
Then in the XAML, without changing the Resource at all - make this the body of your Window:
<StackPanel>
<TextBlock Height="20">
<TextBlock.Text>
<MultiBinding StringFormat="{}Count: {0}">
<Binding Path="MyCollection.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Click="Button_Click" Width="80">Add</Button>
<ContentControl
Margin="30" Height="120"
Content="{Binding CollectionView}" />
</StackPanel>
So now, the Content of your ContentControl is the ICollectionView, and you can tell WPF what the current item is, using the MoveCurrentTo() method.
Note that, even though ICollectionView does not itself contain properties called 'Count' or 'Name', the platform is smart enough to use the underlying data source from the CollectionView in our Bindings...
I am trying to display a tooltip for an item generated by an ItemsControl that needs to pull data from conceptually unrelated sources. For example, say I have an Item class as follows:
public class Item
{
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
I can display the Item within an ItemsControl with a tooltip as follows:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<TextBlock Text="{Binding ItemDescription}" />
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But say I have another property that can be accessed via the DataContext of the ItemsControl. Is there any way to do this from within the tooltip? E.g.,
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" Text="{Bind this to another property of the ItemsControl DataContext}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The code for the test Window I used is as follows:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>() {
new Item() { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item() { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
So in this example I want to show the value of the GlobalText property (in reality this would be another custom object).
To complicate matters, I am actually using DataTemplates and show two different types of objects within the ItemsControl, but any assistance would be greatly appreciated!
After an hour of hair pulling I have come to the conviction that you can't reference another DataContext inside a DataTemplate for a ToolTip. For other Bindings it is perfectly possible as other posters have proven. That's why you can't use the RelativeSource trick either. What you can do is implement a static property on your Item class and reference that:
<Window x:Class="ToolTipSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Name="Root"
xmlns:ToolTipSpike="clr-namespace:ToolTipSpike">
<Grid>
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1"
Text="{Binding Source={x:Static ToolTipSpike:Item.GlobalText},
Path=.}"
/>
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
using System.Collections.Generic;
using System.Windows;
namespace ToolTipSpike
{
public partial class Window1 : Window
{
public List<Item> Items { get; private set; }
public Window1()
{
InitializeComponent();
var itemList = new List<Item>
{
new Item { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.DataContext = this;
}
}
public class Item
{
static Item()
{
GlobalText = "Additional Text";
}
public static string GlobalText { get; set; }
public string ItemName{ get; set;}
public string ItemDescription{ get; set;}
}
}
Second attempt
Ok, the Relative Source Binding doesn't work in this case. It actually works from a data template, you can find many examples of this on the Internets. But here (you were right, David, in your comment) ToolTip is a special beast that is not placed correctly in the VisualTree (it's a property, not a control per se) and it doesn't have access to the proper name scope to use relative binding.
After some more searching I found this article, which describes this effect in details and proposes an implementation of a BindableToolTip.
It might be an overkill, because you have other options -- like using a static property on a class (as in Dabblernl's response) or adding a new instance property to your Item.
First attempt :)
You should consult with the Relative Source Binding types (in this cheat sheet for example):
So your binding will look somehow similar to this:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path= GlobalText}
Almost correct Yacoder, and guessed way wrong there Dabblernl ;)
Your way of thinking is correct and it is possible to reference the DataContext of your ItemsControl
You are missing the DataContext property in path:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.GlobalText}
Second attempt ;)
http://blogs.msdn.com/tom_mathews/archive/2006/11/06/binding-a-tooltip-in-xaml.aspx
Here is an article with the same problem. They can reference the DataContext of their Parent control by the PlacementTarget property:
<ToolTip DataContext=”{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Parent}”>
If you would place the DataContext on a deeper level, you avoid changing your Item DataContext
A second suggestion (Neil and Adam Smith) was that we could use PlacementTarget in the binding. This is nice, as I am actually inheriting the DataContext already from the page that hosts the DataControl, and this would allow the ToolTip to gain access back to the origial control. As Adam noted, though, you have to be aware of the parent/child structure off your markup:
This is a case where I think it's conceptually more appropriate to do this in the view model than it is in the view anyway. Expose the tooltip information to the view as a property of the view model item. That lets the view do what it's good at (presenting properties of the item) and the view model do what it's good at (deciding what information should be presented).
I had a very similar problem and arrived at this question seeking answers. In the end I came up with a different solution that worked in my case and may be useful to others.
In my solution, I added a property to the child item that references the parent model, and populated it when the children were generated. In the XAML for the ToolTip, I then simply referenced the property from the parent model on each element and set the DataContext to the parent model property.
I felt more comfortable with this solution than creating proxy elements in XAML and referencing them.
Using the example code for this question, you would do the following. Note I have not tested this scenario in a compiler, but have done so successfully implemented this solution in the code for my own scenario.
Item:
public class Item
{
public List<Item> Parent { get; set; }
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
Window:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>();
itemList.Add(new Item() { Parent = this, ItemName = "First Item", ItemDescription = "This is the first item." });
itemList.Add(new Item() { Parent = this, ItemName = "Second Item", ItemDescription = "This is the second item." });
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
XAML:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" DataContext={Binding Parent} Text="{Bind this to aproperty of the parent data model}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is a long one . I am adding code so that you can see what I am trying to do. Let me know if anything is not clear
I am trying to get selected items from nested listbox in multiselct mode . Here is code ( removed lot of unwanted stuff)
public class Item
{
public string Name { get; set; }
public IList<Item> SubItems { get; set; } //
public bool IsSelected { get; set; }
}
//Chicken Fried Chicken
//A hearty boneless chicken breast, lightly breaded in our special seasonings and
//golden fried. Served with garlic mashed potatoes, country gravy and seasonal vegetables
// from Applebees
//Item - Chicken Fried Chicken
//SubItem- mashed potatoes
//SubItem- country gravy
//SubItem- seasonal vegetables
//SubItem- Fries
//SubItem- Sauted vegetables
//SubItem- House Salad
public class ItemViewModel : INotifyPropertyChanged, IItemViewModel
{
ObservableCollection<Item> selectedData = new ObservableCollection<Item>();
private ObservableCollection<Item> todaysItems;
public ObservableCollection<Item> TodaysItems
{
get { return todaysItems; }
private set
{
if (todaysItems != value)
{
todaysItems = value;
PropertyChanged(this, new PropertyChangedEventArgs("todaysItems"));
}
}
}
public ItemViewModel(IItemView itemView)
{
this.View = itemView;
this.View.Model = this;
List<Item> items = service.GetAllTestItems();
TodaysItems = new ObservableCollection<Item>(items);
selectedData.CollectionChanged += (sender, e) => UpdateSummary();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void NotifyPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
// How to get Selected Items from ListBox
public ObservableCollection<Item> SelectedData
{
get { return selectedData; }
set
{
selectedData = value;
}
}
private void UpdateSummary()
{
// here I can get selected data , I can find which Item is selected and then update its SubItems IsSelected (CLR) Property
// but something is not right here
}
}
XAML
<UserControl x:Class="ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation"
xmlns:ZCom="clr-namespace:MyProj.Infrastructure;assembly=Infrastructure">
<Grid >
<ListBox ItemsSource="{Binding TodaysItems}">
<ListBox.ItemTemplate>
<DataTemplate >
<Border BorderThickness="1,1,1,1" CornerRadius="2,2,2,2" BorderBrush="Black">
<Grid MinHeight="50" Width="150" Height="Auto" Margin="0,0,0,0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="0"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="4,4,2,2" Grid.Row="0" Width="Auto" TextWrapping="Wrap" Text="{Binding Path=Name}" />
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" >
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource=
{RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}}
}" Value="false">
<Setter Property="Grid.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Margin="2,4,2,2" Grid.Row="0" Width="Auto" FontSize="10" FontStyle="Italic" TextWrapping="Wrap" Text="{Binding Path=Note}"/>
<ListBox Style="{DynamicResource MyStyle}" Grid.Row="1" ItemsSource="{Binding Path=Modifiers}" SelectionMode="Multiple"
ZCom:ListBoxHelper.SelectedItems="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectedData}">
<ListBox.ItemTemplate>
<DataTemplate >
<TextBlock Margin="2,2,2,2" TextWrapping="Wrap" Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
I am using ListBoxHelper ( in Infrastructure) from
http://marlongrech.wordpress.com/2009/06/02/sync-multi-select-listbox-with-viewmodel/
I get the view with Item and SubItems.
1) what is better way to set IsSelected Property of SubItems from nested ListBox
I will add a command which will store selected Item to Database once double clicked. SubItems will be stored as child record based on its IsSelected value.
2) Is there a way to make SubItems property of c# class observable . I would not want to change by adding Observable to the object as it will be in another assembly and may be used by other applications.
Edit 1:
Found somewhat helpful question
WPF Databinding to composite class patterns
But again for this I will have to inherit from INotifyPropertyChanged.
Edit 2:
Let me see if I can explain better- ListBox1 is Single select Mode and parent & ListBox 2 is multiselect. ListBox1 is binded (item source) to property which returns observablecollection. ListBox2 is binded to a Property in Item Class (Item.SubItems) which returns IList. Item Class has IsSelected Property.I want to be able to select subitems which should set IsSelected Property for the subItems to true. Knowing that there is no INotifyPropertyChanged inheritance in Item Class how can I achieve this. I assume that unless subitems come under some observable collection any changes will not be notified back to source. Using the selectedData property I can update the subitems by finding parent Item but then to update the view I will have to firePropertChanged for "items" which involves all items and subitems. I want only subitems change to be notified back by binding mechanism. Sorry if I am still not clear.
Edit 3:
I Guess there is no way but to implement INotifyPropertyChanged on Item class. Other way would be to implemnt a viewmodel which is very specific to needs of view but this will add up lot of code.
It's a little confusing what your overall goal here is.
If you're merely trying to get the selected items from a nested ListBox, Have you tried giving your ListBox an x:Name and exposing your SelectedItems via a property in your UserControl (ItemView) class?
public IList SelectedItems
{
get { return nestedListBox.SelectedItems; }
}
I have a tabControl that is bound to an observable collection.
In the headerTemplate, I would like to bind to a string property, and in the contentTemplate I have placed a user-control.
Here's the code for the MainWindow.xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="contentTemplate">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="itemTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</Grid.Resources>
<TabControl IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Pages}"
ItemTemplate="{StaticResource itemTemplate}"
ContentTemplate="{StaticResource contentTemplate}"/>
</Grid>
And its code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public ObservableCollection<PageViewModel> Pages { get; set; }
public MainWindowViewModel()
{
this.Pages = new ObservableCollection<PageViewModel>();
this.Pages.Add(new PageViewModel("first"));
this.Pages.Add(new PageViewModel("second"));
}
}
public class PageViewModel
{
public string Name { get; set; }
public PageViewModel(string name)
{
this.Name = name;
}
}
So the problem in this scenario (having specified an itemTemplate as well as a controlTemplate) is that I only get one instance for the user-control, where I want to have an instance for each item that is bound to.
Try this:
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Pages}">
<TabControl.Resources>
<DataTemplate x:Key="contentTemplate" x:Shared="False">
<local:UserControl1/>
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ContentTemplate" Value="{StaticResource contentTemplate}"/>
</Style>
</TabControl.Resources>
</TabControl>
Try setting
x:Shared="False"
When set to false, modifies Windows Presentation Foundation (WPF) resource retrieval behavior such that requests for a resource will create a new instance for each request, rather than sharing the same instance for all requests.
You need to override the Equals() Method of your PageViewModel class.
public override bool Equals(object obj)
{
if (!(obj is PageViewModel)) return false;
return (obj as PageViewModel).Name == this.Name;
}
Something like this should work.
Now it is looking for the same property of the value Name. Otherwise you could also add a ID Property which is unique.