I am trying to proper bind several collection by using composite collection.
Please point out my mistake
I am expecting something like
Car
Year
2014
2013
Name
Toyota
Euro
Benz
But Instead I am getting
Car
2014
2013
Toyota
Benz
XAML:
<TreeView Margin="8" Grid.Row="2" Grid.RowSpan="2" Name="CarTree" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Car}" ItemsSource="{Binding Children}">
<StackPanel>
<TextBlock Text="{Binding Path=CarName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Year}">
<StackPanel>
<TextBlock Text="{Binding Path=YearName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Year Class:
public class Year:ViewModelBase
{
private string _yearname= String.Empty;
public string YearName
{
get { return _yearname; }
set
{
_yearname= value;
OnPropertyChanged("YearName");
}
}
}
Car Class:
public class Car: ViewModelBase
{
private string _carname= String.Empty;
public string CarName
{
get { return _carname; }
set
{
//ignore if values are equal
if (value == _carname) return;
_carname= value;
OnPropertyChanged("CarName");
}
}
private ObservableCollection<Year> _years=new ObservableCollection<Year>();
public ObservableCollection<Year> Years
{
get { return _years; }
set
{
//ignore if values are equal
if (value == _years) return;
_years= value;
OnPropertyChanged("Years");
}
}
public IList Children
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = this.Years},
new CollectionContainer() { Collection = ...},
new CollectionContainer() { Collection =... }
};
}
}
}
Maybe you'd better not use hierarchical data templates as your data structure does not seem recursive.
But if you want to keep them you can do something like:
public class YearsCollection : ObservableCollection<Year>
{
}
...
YearsCollection _years = new YearsCollection();
public YearsCollection Years
{
get { return _years; }
set
{
//ignore if values are equal
if (value == _years) return;
_years = value;
OnPropertyChanged("Years");
}
}
public IList Children
{
get
{
return new[]
{
this.Years
...
};
}
}
And add a template for each intermediate collection:
<HierarchicalDataTemplate DataType="{x:Type local:YearsCollection}" ItemsSource="{Binding}">
<StackPanel>
<TextBlock Text="Years" />
</StackPanel>
</HierarchicalDataTemplate>
Related
I want to have a conditional XAML that selects whether this TreeView.Resources or the other.
I am using MaterialDesignInXAML toolkit's Card and inside it is the TreeView.
Let's say I have a collection of Fruits that has another collection inside called Trees.
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type domain:Tree}" ItemsSource="{Binding Fruits}">
// tree information here ...
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type domain:Fruit}">
// fruit information here ...
</DataTemplate>
</TreeView.Resources>
Problem here is, there are fruits that don't grow from trees. So what I want is to have another resource but still will continue the template.
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type domain:Fruit}">
// fruit information here
</HierarchicalDataTemplate>
</TreeView.Resources>
I want my output to be look like this:
> Apple Tree
- Apples
> Mango Tree
- Mangoes
> Watermelon
Edit:
I used #BionicCode's second suggestion which is to use DataTemplateSelector. I added this to my XAML:
<UserControl DataContext="{Binding ViewModel}">
<UserControl.Resources>
<domain:DtmSelector x:Key="DtmSelector">
<domain:DtmSelector.WithTree>
<HierarchicalDataTemplate DataType="{x:Type domain:Tree}" ItemsSource="{Binding Fruits}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type domain:Fruit}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</domain:DtmSelector.WithTree>
<domain:DtmSelector.WithoutTree>
<DataTemplate DataType="{x:Type domain:Tree}">
<TextBlock Text="{Binding Fruits[0].Name}"/>
</DataTemplate>
</domain:DtmSelector.WithoutTree>
</domain:DtmSelector>
</UserControl.Resources>
<TreeView
ItemsSource="{Binding Trees}"
ItemTemplateSelector="{StaticResource DtmSelector}"
/>
</UserControl>
And in my DtmSelector.cs:
public class DtmSelector: DataTemplateSelector
{
public DataTemplate WithTrees { get; set; }
public DataTemplate WithoutTrees { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Tree key = item as Tree;
if (key.Fruits.Count() == 0)
{
return WithTrees;
}
else
{
if (key.Fruits.Select(c=>c.Name).Contains(key.Name))
{
return WithTrees;
}
else
{
return WithoutTrees;
}
}
}
}
My ViewModel's concept is like this:
foreach (var item in fruits)
{
if ( item.Tree == null)
{
if (!Trees.Select(c => c.Name).Contains(item.Name))
{
Trees.Add(new Tree(item.Name));
}
foreach (var tree in Trees.ToList())
{
if (tree.Name == item.Name)
{
tree.Fruits.Add(item);
}
}
}
else
{
if (!Trees.Select(c => c.Name).Contains(item.Tree.Name))
{
Trees.Add(item.Tree);
}
foreach (var tree in Trees.ToList())
{
if (tree.Name == item.Tree.Name)
{
tree.Fruits.Add(item);
}
}
}
}
PROBLEM
This set of codes will check first the Trees without its Fruits being added. So this line if (key.Fruits.Count() == 0) will always return true. Is there something I can do? Am I missing something?
I found an answer from another thread that could help, however, I don't on how this will work on the ViewModel, on how can I select a certain setter by the binded Property.
You have some options here. I will show you two suggestions based on the data structure (model) design.
First suggestion
Use a single data type for the complete tree structure:
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
ViewModel()
{
this.Fruits = new ObservableCollection<Fruit>()
{
new Fruit("Apple Tree", new ObservableCollection<Fruit>() {new Fruit("Apples")}),
new Fruit("Mango Tree", new ObservableCollection<Fruit>() {new Fruit("Mangos")}),
new Fruit("Watermelon")
}
ObservableCollection<Fruit> fruits;
ObservableCollection<Fruit> Fruits
{
get => this.fruits;
set
{
this.fruits = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}
Fruit.cs (data model)
class Fruit : INotifyPropertyChanged
{
Fruit(string name) : this(name, new List<Fruit>())
{
}
Fruit(string name, IEnumerable<Fruit> children)
{
this.Name = name;
this.Children = new ObservableCollection<Fruit>(children ?? new List<Fruit>());
}
string name;
string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
ObservableCollection<Fruit> children;
ObservableCollection<Fruit> Children
{
get => this.children;
set
{
this.children = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}
TreeView XAML
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<TreeView ItemsSource={Binding Fruits}>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type domain:Fruit}" ItemsSource="{Binding Children}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type domain:Fruit}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
Second suggestion
In case you need different types for the different roles (nodes) you have to make use of a DataTemplateSelector:
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
ViewModel()
{
this.Fruits = new ObservableCollection<INode>()
{
new Category("Apple Tree", new ObservableCollection<INode>() {new Fruit("Apples")}),
new Category("Mango Tree", new ObservableCollection<INode>() {new Fruit("Mangos")}),
new Fruit("Watermelon")
}
ObservableCollection<INode> fruits;
ObservableCollection<INode> Fruits
{
get => this.fruits;
set
{
this.fruits = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}
INode.cs (data model)
// Common base type for the node collection and all tree nodes
interface INode: INotifyPropertyChanged
{
string Name {get; set; }
ObservableCollection<INode> Children { get; set; }
}
Category.cs (data model)
class Category : INode
{
Category(string name) : this(name, new List<INode>())
{
}
Category(string name, IEnumerable<INode> children)
{
this.Name = name;
this.Children = new ObservableCollection<INode>(children ?? new List<INode>());
}
string name;
string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
ObservableCollection<INode> children;
ObservableCollection<INode> Children
{
get => this.children;
set
{
this.children = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}
Fruit.cs (data model)
class Fruit : INode
{
Fruit(string name) : this(name, new List<INode>())
{
}
Fruit(string name, IEnumerable<INode> children)
{
this.Name = name;
this.Children = new ObservableCollection<INode>(children ?? new List<INode>());
}
string name;
string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
ObservableCollection<INode> children;
ObservableCollection<INode> Children
{
get => this.children;
set
{
this.children = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}
NodeTemplateSelector.cs
class NodeTemplateSelector : DataTemplateSelector
{
public DataTemlplate CategoryNodeTemplate { get; set; }
public DataTemlplate FruitNodeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
switch (item)
{
case Category _:
return this.CategoryNodeTemplate;
case Fruit _:
return this.FruitNodeTemplate;
default:
return this.FruitNodeTemplate;
}
}
}
TreeView XAML
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<Window.Resources>
<NodeTemplateSelector x:Key="NodeTemplateSelector">
<NodeTemplateSelector.CategoryNodeTemplate>
<HierarchicalDataTemplate DataType="{x:Type Category}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</NodeTemplateSelector.CategoryNodeTemplate>
<NodeTemplateSelector.FruitNodeTemplate>
<DataTemplate DataType="{x:Type Fruit}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</NodeTemplateSelector.FruitNodeTemplate>
</NodeTemplateSelector>
</Window.Resources>
<TreeView ItemsSource="{Binding Fruits}"
ItemTemplateSelector="{StaticResource NodeTemplateSelector}" />
</Window>
Here is a class with undefined variable that needs to be passed into the WPF window.
public class SelectedVal<T>
{
public T val {get;set;}
}
Window:
public partial class SOMEDialogue : Window
{
public List<SelectedVal<T>> returnlist { get { return FullList; } }
public List<SelectedVal<T>> FullList = new List<SelectedVal<T>>();
public SOMEDialogue (List<SelectedVal<T>> inputVal)
{
InitializeComponent();
}
}
So here is the question, how can I do this properly to get the T and have a global variable set in my WPF?
Edited (code edited too):
The purpose for the WPF is:
A list of SelectedVal<T> input
Display this input in this WPF
Depend on the T type, user can do something about this input
When finished a return List<SelectedVal<T>> returnlist can be
accessed
This is the basic idea I'm describing. Let me know if you hit any snags. I'm guessing that the search text and the min/max int values are properties of the dialog as a whole. I'm also assuming that there may be a mixture of item types in the collection, which may be an assumption too far. Can you clarify that?
Selected value classes
public interface ISelectedVal
{
Object Val { get; set; }
}
public class SelectedVal<T> : ISelectedVal
{
public T Val { get; set; }
object ISelectedVal.Val
{
get => this.Val;
set => this.Val = (T)value;
}
}
public class StringVal : SelectedVal<String>
{
}
public class IntVal : SelectedVal<int>
{
}
Dialog Viewmodel
public class SomeDialogViewModel : ViewModelBase
{
public SomeDialogViewModel(List<ISelectedVal> values)
{
FullList = values;
}
public List<ISelectedVal> FullList { get; set; }
private String _searchText = default(String);
public String SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged();
}
}
}
private int _minInt = default(int);
public int MinInt
{
get { return _minInt; }
set
{
if (value != _minInt)
{
_minInt = value;
OnPropertyChanged();
}
}
}
private int _maxInt = default(int);
public int MaxInt
{
get { return _maxInt; }
set
{
if (value != _maxInt)
{
_maxInt = value;
OnPropertyChanged();
}
}
}
}
.xaml.cs
public SOMEDialogue (List<ISelectedVal> inputValues)
{
InitializeComponent();
DataContext = new SomeDialogViewModel(inputValues);
}
XAML
<Window.Resources>
<DataTemplate DataType="{x:Type local:StringVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Search text:</Label>
<TextBox Text="{Binding DataContext.SearchText, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:IntVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Min value:</Label>
<TextBox Text="{Binding DataContext.MinIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<Label>Max value:</Label>
<TextBox Text="{Binding DataContext.MaxIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl
ItemsSource="{Binding FullList}"
/>
</Grid>
I am using MVVM and trying to represent my ViewModel data in View.
I have a class called Track containing list of Variations. I want to represent each variation as a TextBlock using data binding.
I am able to represent a single track as:
<Window.Resources>
<src:Track x:Key="trck"/>
...
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource trck}}" Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Vars}" Height="53" Width="349">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
I also have a class called TrackList containing collection of Tracks.
I tried to use HierarchicalDataTemplate to represent Hierarchical Data of TrackList.
But it's not working..
I am new to WPF, and I have tried the below things so far:
<DockPanel.Resources>
<DataTemplate DataType="{x:Type src:Variation}">
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type src:Track}" ItemsSource = "{Binding Path=Vars}">
<StackPanel/>
</HierarchicalDataTemplate>
</DockPanel.Resources>
public class TrackList : ViewModel
{
private ICollection<Track> tracks;
private Track selectedTrack;
public string Name
{ get; set; }
public TrackList()
{
this.tracks = new List<Track>();
this.tracks.Add(new Track("T1"));
this.tracks.Add(new Track("T2"));
Name = "Track List";
selectedTrack = tracks.ElementAt(1);
}
public ICollection<Track> Tracks
{
get { return this.Tracks; }
set { this.Tracks = value; }
}
public Track SelectedTrack
{
get { return this.selectedTrack; }
set
{
if (this.selectedTrack != value)
{
this.selectedTrack = value;
this.OnPropertyChanged("SelectedTrack");
}
}
}
}
public class Track : ViewModel
{
private ICollection<Variation> vars;
private Variation selectedVar;
public string Name { get; set; }
public Track()
{
Init();
}
public Track(string p)
{
// TODO: Complete member initialization
this.Name = p;
Init();
}
private void Init()
{
this.vars = new List<Variation>();
this.vars.Add(new Variation("var1", 20, Brushes.Red));
this.vars.Add(new Variation("var2", 60, Brushes.Green));
this.vars.Add(new Variation("var3", 40, Brushes.Khaki));
this.vars.Add(new Variation("var4", 120, Brushes.Aqua));
selectedVar = vars.ElementAt(1);
}
public ICollection<Variation> Vars
{
get { return this.vars; }
set { this.vars = value; }
}
public Variation SelectedVar
{
get { return this.selectedVar; }
set
{
if (this.selectedVar != value)
{
this.selectedVar = value;
this.OnPropertyChanged("SelectedVar");
}
}
}
}
public class Variation : ViewModel
{
public int elapsedtime { get; set; }
public string Name { get; set; }
public System.Windows.Media.Brush color { get; set; }
public Variation(string varname)
{
Name = varname;
}
public Variation(string name, int time, System.Windows.Media.Brush br)
{
// TODO: Complete member initialization
this.Name = name;
this.elapsedtime = time;
this.color = br;
}
}
public abstract class ViewModel : INotifyPropertyChanged
{
private readonly Dispatcher _dispatcher;
protected ViewModel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected Dispatcher Dispatcher
{
get { return _dispatcher; }
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
Please let me know for any farther information.
Thanks
I don't think you need HierarchicalDataTemplate, your tree has known number of levels (TrackList>Track>Variation). You can simply do this:
<DockPanel.Resources>
<DataTemplate DataType="{x:Type src:Variation}">
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type src:Track}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ItemsControl ItemsSource="{Binding Vars}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<ItemsControl ItemsSource="{Binding Tracks}" />
Where ItemsControl bind to Tracks property of the TrackList (ItemsControl.DataContext = TrackList).
You can represent your hierarchical data using a TreeView:
<TreeView ItemsSource="{Binding Tracks}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type src:Track}" ItemsSource="{Binding Vars}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I have a TreeView with Binding, but in the TreeView only 1st level items are shown. I need a treeview =) I broke my head what is wrong.
Here is my code:
MainWindow.xaml
<TreeView Margin="2.996,10,214,10" ItemsSource="{Binding Path=Urls}" Grid.Column="1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<Grid>
<Rectangle Fill="{Binding Path=Color}" HorizontalAlignment="Left" Stroke="Black" VerticalAlignment="Top" Height="20" Width="20"/>
<TextBlock Text="{Binding Path=AbsoluteUrl}" Margin="25,0,0,0" />
</Grid>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=AbsoluteUrl}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
MainViewModel.cs (part)
public ObservableCollection<Url> Urls { get; set; }
public MainWindowViewModel()
{
Urls = new ObservableCollection<Url>();
}
Url.cs
class Url : INotifyPropertyChanged
{
public Url() { }
public Url(string absoluteUrl, bool isBroken, string color)
{
AbsoluteUrl = absoluteUrl;
IsBroken = isBroken;
Color = color;
}
enum Status { Working, Broken };
private ObservableCollection<Url> childUrlsValue = new ObservableCollection<Url>();
public ObservableCollection<Url> ChildUrls
{
get
{
return childUrlsValue;
}
set
{
childUrlsValue = value;
}
}
private string _absoluteUrl;
public string AbsoluteUrl
{
get { return _absoluteUrl; }
set
{
if (_absoluteUrl != value)
{
_absoluteUrl = value;
OnPropertyChanged("AbsoluteUrl");
}
}
}
private bool _isBroken;
public bool IsBroken
{
get { return _isBroken; }
set
{
if (_isBroken != value)
{
_isBroken = value;
OnPropertyChanged("IsBroken");
}
}
}
private string _color;
public string Color
{
get { return _color; }
set
{
if (_color != value)
{
_color = value;
OnPropertyChanged("Color");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And just about this i'm adding items to Urls:
Url DataGridTopic = new Url(startUrl.ToString(), true, "red");
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
Urls.Add(DataGridTopic);
You will have to tell the HierarchicalDataTemplate where to get the child items of a node from by using its ItemsSource property.
In your case:
<HierarchicalDataTemplate
DataType="{x:Type my:Url}"
ItemsSource="{Binding Path=ChildUrls}"
>
...
</HierarchicalDataTemplate>
Note also the usage od the DataType attribute, which often will become a necessity if the levels of the tree are made of different object types (a tree of directories and files would be such an example). However, i am not sure whether this would apply to your scenario or not.
I created a new TextBlock class which has ItemsSource property and translates that ItemsSource into "Run" object:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class ImageOption : ISection
{
private InlineUIContainer mContainer;
public ImageOption(string aDisplay)
{
Image lImage;
lImage = new Image();
lImage.Source = new BitmapImage(new Uri(Environment.CurrentDirectory + aDisplay));
lImage.Height = 15;
lImage.Width = 15;
mContainer = new InlineUIContainer(lImage);
}
public Inline GetDisplayElement()
{
return mContainer;
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableCollection<ISection>), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata(new ObservableCollection<ISection>(),
new PropertyChangedCallback(SetContent)));
public ObservableCollection<ISection> ItemsSource
{
get
{
return GetValue(ItemsSourceProperty) as ObservableCollection<ISection>;
}
set
{
if (ItemsSource != null)
ItemsSource.CollectionChanged -= CollectionChanged;
SetValue(ItemsSourceProperty, value);
SetContent();
ItemsSource.CollectionChanged += CollectionChanged;
}
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
SetContent();
}
private static void SetContent(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DependencyObject lParent = d;
MultiTypeDynamicTextBlock lPanel = lParent as MultiTypeDynamicTextBlock;
if (lPanel != null)
{
lPanel.ItemsSource = e.NewValue as ObservableCollection<ISection>;
}
}
private void SetContent()
{
if (ItemsSource != null)
{
Inlines.Clear();
foreach (ISection lCurr in ItemsSource)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
If I Bind the ItemsSource directly to the DataContext, it works.
But if I bind it to an object that changes at runtime (such as SelectedItem on a ListBox) it doesn't update the text when a new item is selected.
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding Items}"/>
</StackPanel>
</StackPanel>
Any reason why?
In your example, does the SelectedItem has two properties Title and Items? Or is Items a property in your viewmodel? If the answer is the latter, than you can find a solution below.
I don't entirely understand what you mean, but I'll give it a try.
If you mean that the ItemsSource on your custom control isn't set, than you have to point XAML into the right direction.
Below you can find a solution, if this is what you want to achieve.
What I did is pointing the compiler to the right source with this line of code:
ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"
Here you say that the compiler can find the Binding property in the DataContext of the Window (or any control where you can find the property).
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"/>
</StackPanel>
</StackPanel>
Hopefully this helped.
EDIT
The title property will changes when I select another one from the ListBox.
If Items is set to a new ObservableCollection, do you call the OnPropertyChanged event for Items when the SelectedItem changes?
OnPropertyChanged("Items");
Thank you for your help.
I managed to fix this by updating the MultiTypeDynamicTextBlock as follows:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
ISection Clone();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
public ISection Clone()
{
return new TextOption(mText.Text);
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class SectionList
{
private ObservableCollection<ISection> mList;
public Action CollectionChanged;
public ObservableCollection<ISection> Items
{
get
{
ObservableCollection<ISection> lRet = new ObservableCollection<ISection>();
foreach (ISection lCurr in mList)
{
lRet.Add(lCurr.Clone());
}
return lRet;
}
}
public int Count { get { return mList.Count; } }
public SectionList()
{
mList = new ObservableCollection<ISection>();
}
public void Add(ISection aValue)
{
mList.Add(aValue);
}
public SectionList Clone()
{
SectionList lRet = new SectionList();
lRet.mList = Items;
return lRet;
}
}
public MultiTypeDynamicTextBlock()
{
}
public static readonly DependencyProperty ItemsCollectionProperty =
DependencyProperty.Register("ItemsCollection", typeof(SectionList), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata((PropertyChangedCallback)((sender, args) =>
{
MultiTypeDynamicTextBlock textBlock = sender as MultiTypeDynamicTextBlock;
SectionList inlines = args.NewValue as SectionList;
if (textBlock != null)
{
if ((inlines != null) && (inlines.Count > 0))
{
textBlock.ItemsCollection.CollectionChanged += textBlock.ResetInlines;
textBlock.Inlines.Clear();
foreach (ISection lCurr in textBlock.ItemsCollection.Items)
{
textBlock.Inlines.Add(lCurr.GetDisplayElement());
}
}
else
{
inlines = new SectionList();
inlines.Add(new TextOption("No value set"));
textBlock.ItemsCollection = inlines;
}
}
})));
public SectionList ItemsCollection
{
get
{
return (SectionList)GetValue(ItemsCollectionProperty);
}
set
{
SectionList lTemp;
if (value == null)
{
lTemp = new SectionList();
lTemp.Add(new TextOption("No value set for property"));
}
else
{
lTemp = value;
}
SetValue(ItemsCollectionProperty, lTemp);
}
}
private void ResetInlines()
{
Inlines.Clear();
foreach (ISection lCurr in ItemsCollection.Items)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
And I update the fields that were Binded to be of type MultiTypeDynamicTextBlock.SectionList
As long as I am using a copy (Clone) it is working, for some reason when I don't clone it removes the value from the display in the list, if someone knows why I would love to learn but I managed to go around it.
the XAML of the window is:
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding GeneralItems}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsCollection="{Binding Items}" Margin="20,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding GeneralItems, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock DataContext="{Binding Items}" ItemsCollection="{Binding}" Margin="20,0,0,0"/>
</StackPanel>
</StackPanel>