In short, the goal is to propagate LabelWidth to its children in my UserControl class, PropertyView. See this fragment:
<TabItem.Header>Press</TabItem.Header>
<TabItem.DataContext>
<Binding XPath="press_information"/>
</TabItem.DataContext>
<W3V:PropertyView LabelWidth="200"></W3V:PropertyView>
ANSWER (credit to Athari for his part). To make it work, I needed two elements: In C#, a dependency property:
public double LabelWidth
{ get { return (double)this.GetValue(LabelWidthProperty); }
set { this.SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register(
"LabelWidth", typeof(double), typeof(PropertyView), new PropertyMetadata(100.0)
);
In XAML, the following binding syntax:
<W3V:SimpleControl x:Name="simple" Content="{Binding}"
LabelWidth="{Binding LabelWidth,
RelativeSource={RelativeSource AncestorType=W3V:PropertyView}}" />
What didn't work (my original problem):
See the ????? below in the XAML code. I have NO IDEA what I can put in to make it so the SimpleControl will get a LabelWidth assigned, so that it will set its TextBlock's Width property.
I don't even care what approach is taken, it just needs to deal with the fact that PropertyView is bound to an XML object so it can display its properties, and LabelWidth needs to be a property the control-user sets that gets shoved down into the control. LabelWidth will vary depending on what object is being displayed, so it can't be global.
<UserControl x:Class="W3.Views.PropertyView" ... >
<UserControl.Resources>
</UserControl.Resources>
<StackPanel Margin="2" CanVerticallyScroll="true">
<Border Height="22">
<TextBlock VerticalAlignment="Bottom"
Text="{Binding XPath=#label}"
FontSize="16" FontWeight="Bold" />
</Border>
<ItemsControl ItemsSource="{Binding XPath=*}" Margin="20,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<W3V:SimpleControl x:Name="simple"
Content="{Binding}"
LabelWidth=?????? />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
C#:
public partial class PropertyView : UserControl
{
public double LabelWidth
{
get { return (double)this.GetValue(LabelWidthProperty); }
set { this.SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register(
"LabelWidth2", typeof(double), typeof(PropertyView), new PropertyMetadata(0.0)
);
public PropertyView()
{
InitializeComponent();
}
}
I've searched extensively for a solution that deals with this combination of circumstances, tried many things without success (well, success for simpler situations, but not this), and I'm at a loss here.
Answer to edited question
Here's another go for your problem based on refined question. I'm still not 100% sure what you are trying to achieve but maybe the following points you to right direction, at least.
So there's a Window containing only UserControl. This user control is bound to XML file and it has three label fields. One shows node attritube and the following XML file content. First label's width is bound to property in MainWindow and the other to static converter resource inside the UserControl.
MainWindow XAML
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:user="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="600">
<Grid>
<user:XmlUserControl />
</Grid>
</Window>
MainWindow Codebehind
using System;
using System.Windows;
namespace WpfApplication
{
public partial class MainWindow : Window
{
readonly Random _random = new Random();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public double ControlWidth
{
get { return _random.Next(200, 600); }
}
}
}
XmlUserControl XAML
<UserControl x:Class="WpfApplication.XmlUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="350">
<StackPanel>
<StackPanel.Resources>
<XmlDataProvider x:Key="XmlData" XPath="Data/Items" Source="Items.xml" />
<local:NodeWidthConverter x:Key="NodeWidthConverter" />
</StackPanel.Resources>
<ItemsControl>
<ItemsControl.ItemsSource>
<Binding Source="{StaticResource XmlData}" XPath="*"/>
</ItemsControl.ItemsSource>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label x:Name="Title" VerticalAlignment="Bottom"
FontWeight="Bold" HorizontalAlignment="Left"
Content="{Binding XPath=#Title}" />
<Label Background="BurlyWood" HorizontalAlignment="Left"
Content="{Binding}" Width="{Binding ControlWidth}" />
<Label Background="BlanchedAlmond" HorizontalAlignment="Left"
Content="{Binding}" Width="{Binding ElementName=Title, Converter={StaticResource NodeWidthConverter}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
XmlUserControl codebehind
using System.Windows.Controls;
namespace WpfApplication
{
public partial class XmlUserControl : UserControl
{
public XmlUserControl()
{
InitializeComponent();
}
}
}
NodeWidthConverter
using System;
using System.Globalization;
using System.Windows.Data;
namespace WpfApplication2
{
public class NodeWidthConverter : IValueConverter
{
public static Random Random = new Random();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Random.Next(200, 600);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Items.xml sample data
<Data>
<Items>
<Item Title="Title 1">
<Name>This is item name 1</Name>
<Summary>Summary for item 1</Summary>
</Item>
<Item Title="Title 2">
<Name>This is item name 2</Name>
<Summary>Summary for item 2</Summary>
</Item>
<Item Title="Title 3">
<Name>This is item name 3</Name>
<Summary>Summary for item 3</Summary>
</Item>
</Items>
</Data>
With this you'll get the following. Label has colored background to visualize the changing width properties.
Hope this helps!
So, you just need to bind SimpleControl.LabelWidth to PropertyView.LabelWidth? It can be achieved this way:
<W3V:SimpleControl
LabelWidth="{Binding Path=LabelWidth,
RelativeSource={RelativeSource AncestorType=PropertyView}}"
P.S. Your dependency property is registered as "LabelWidth2" (typo?). And new PropertyMetadata(0.0) is redundant, as default(double) == 0.0.
Do you have a failed binding back to your StaticResources? Do you have values defined in the resources section? Try something like this (note that I wrote this directly here (not in VS), it should be pretty much correct :)
<UserControl ...>
<UserControl.Resources>
<System.Int x:Key="ContentWidth">100</System.Int>
</UserControl.Resources>
<StackPanel Orientation="Horizontal" >
<TextBlock Width="{StaticResource LabelWidth}" Text="test"/>
<TextBox Width="{StaticResource ContentWidth}" />
</StackPanel>
</UserControl>
I would ask if you are really intending to go to StaticResources or whether you meant to bind to a property on a viewmodel or in the code behind of the view?
Related
Background
I am making a custom control that has multiple ListBox's. I want to make this control MVVM compliant, so I am keeping any XAML and the code behind agnostic with respect to any ViewModel. One ListBox is simply going to be a list of TextBox's while the other is going to have a canvas as the host to display the data graphically. Both of these ListBox's are children of this custom control.
Pseudo example for the custom control template:
<CustomControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"/>
<ListBox2 Grid.Column="1"/>
</CustomControl>
The code behind for this custrom control would have a dependency property that will serve as the ItemsSource, fairly standard stuff:
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
Where I am stuck
Because the two ListBox's are using the same data source but just display the data differently, I want the ItemsSource defined as one of the the parent view's dependency properties to be the ItemsSource for the two children. From the ViewModel side, this items source can be some sort of ObservableCollection<ChildViewModels>, or IEnumerable, or whatever it wants to be.
How can I point to properties from the ItemsSource's ViewModel to dependency properties of the child views?
I was hoping to get something similar to how it could be done outside of a custom view:
Example Parent ViewModel(omitting a lot, assume all functioning):
public class ParentViewModel
{
public ObservableCollection<ChildViewModel> ChildViewModels;
}
Example ViewModel (omitting INotifyPropertyChanged and associated logic):
public class ChildViewModel
{
public string Name {get; set;}
public string ID {get; set;}
public string Description {get; set;}
}
Example control (ommitting setting the DataContext, assume set properly):
<ListBox ItemsSource="{Binding ChildViewModels}">
<ListBox.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</ListBox.ItemsTemplate>
</ListBox>
How can I do something similar where I can pass the properties from the ItemsSource to the child views on a custom control?
Many thanks
If I understand correctly what you need, then here is an example.
Add properties for element templates in both lists and style for Canvas.
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace Core2022.SO.jgrmn
{
public class TwoListControl : Control
{
static TwoListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoListControl), new FrameworkPropertyMetadata(typeof(TwoListControl)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(TwoListControl),
new PropertyMetadata((d, e) => ((TwoListControl)d).OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue)));
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
//throw new NotImplementedException();
}
public DataTemplate TemplateForStack
{
get { return (DataTemplate)GetValue(TemplateForStackProperty); }
set { SetValue(TemplateForStackProperty, value); }
}
public static readonly DependencyProperty TemplateForStackProperty =
DependencyProperty.Register(
nameof(TemplateForStack),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public DataTemplate TemplateForCanvas
{
get { return (DataTemplate)GetValue(TemplateForCanvasProperty); }
set { SetValue(TemplateForCanvasProperty, value); }
}
public static readonly DependencyProperty TemplateForCanvasProperty =
DependencyProperty.Register(
nameof(TemplateForCanvas),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public Style StyleForCanvas
{
get { return (Style)GetValue(StyleForCanvasProperty); }
set { SetValue(StyleForCanvasProperty, value); }
}
public static readonly DependencyProperty StyleForCanvasProperty =
DependencyProperty.Register(
nameof(StyleForCanvas),
typeof(Style),
typeof(TwoListControl),
new PropertyMetadata(null));
}
}
In the theme (Themes/Generic.xaml), set bindings to these properties:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jgrmn="clr-namespace:Core2022.SO.jgrmn">
<Style TargetType="{x:Type jgrmn:TwoListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type jgrmn:TwoListControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForStack}"/>
<ListBox Grid.Column="1"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForCanvas}"
ItemContainerStyle="{TemplateBinding StyleForCanvas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Window with an example of use:
<Window x:Class="Core2022.SO.jgrmn.TwoListWindow"
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"
xmlns:local="clr-namespace:Core2022.SO.jgrmn"
mc:Ignorable="d"
Title="TwoListWindow" Height="250" Width="400">
<FrameworkElement.DataContext>
<CompositeCollection>
<Point>15 50</Point>
<Point>50 150</Point>
<Point>150 50</Point>
<Point>150 150</Point>
</CompositeCollection>
</FrameworkElement.DataContext>
<Grid>
<local:TwoListControl ItemsSource="{Binding}">
<local:TwoListControl.TemplateForStack>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}Point ({0} {1})">
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</local:TwoListControl.TemplateForStack>
<local:TwoListControl.TemplateForCanvas>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Red"/>
</DataTemplate>
</local:TwoListControl.TemplateForCanvas>
<local:TwoListControl.StyleForCanvas>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</local:TwoListControl.StyleForCanvas>
</local:TwoListControl>
</Grid>
</Window>
You must spend all participating controls a ItemsSource property. The idea is to delegate the source collection from the parent to the child controls and finally to the ListBox. The ItemsSource properties should be a dependency property of type IList and not IEnumerable. This way you force the binding source to be of type IList which improves the binding performance.
To allow customization of the actual displayed items, you must either
a) spend every control a ItemTemplate property of type DataTemplate and delegate it to the inner most ListBox.ItemTemplate (similar to the ItemsSource property) or
b) define the template as a resource (implicit template, which is a key less DataTemplate).
The example implements a):
<Window>
<Window.DataContext>
<ParentViewModel />
</Window.DataCOntext>
<CustomControl ItemsSource="{Binding ChildViewModels}">
<CustomControl.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</CustomControl.ItemsTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
<ListBox2 Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</CustomControl>
</Window>
Inside the child controls (ListBox1 and ListBox2):
<UserControl>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>
In this WPF tutorial in level 2, the author creates the viewModel in Window.Resources like so:
<Window.Resource>
<local:myViewModel x:Key="viewmodel"/>
</Window.Resource>
and binds each value with {Binding myValue, Source={StaticResource myViewModel}}, however other similar tutorials sets Window.DataContext to the viewModel like so:
<Window.DataContext>
<local:myViewModel />
</Window.DataContext>
then simply binds values using {Binding myValue}.
My question is: Is there an appreciable difference between them, or is this a user preference?
There is a semantic difference.
If multiple controls reference the static resource, they all refer to the same object.
If you set the DataContext of your UI elements to an instance of your model class, each element gets its own instance.
To illustrate, consider this model class:
public class Model
{
private static int counter;
private readonly int id;
public Model()
{
id = counter++;
}
public override string ToString()
{
return id.ToString();
}
}
...and some XAML snippets that use it:
<Window.Resources>
<wpf:Model x:Key="ModelResource"/>
</Window.Resources>
...
<StackPanel HorizontalAlignment="Center" Margin="20" Orientation="Horizontal">
<Button Content="{StaticResource ModelResource}" />
<Button Content="{StaticResource ModelResource}" />
<Button Content="{Binding}">
<Button.DataContext>
<wpf:Model />
</Button.DataContext>
</Button>
<Button Content="{Binding}">
<Button.DataContext>
<wpf:Model />
</Button.DataContext>
</Button>
<Button Content="{StaticResource ModelResource}" />
</StackPanel>
The output:
The recommendations I see for the individual items are to use a TextBlock with TextWrapping="true" for for former, and a Viewbox for the latter. However the two don't play nicely together. The only one I've seen for combining the two was to explicitly set a [Width on the TextBlock][1], but that requires knowing the width of the text in advanced because different lengths of text only work out nicely with different widths making in unsuitable for use with templating because the ideal length will be variable and not known in advance.
Without setting an explicit width what I get is:
Which works OK for the two single word items, but the multi-word one would fill the area much better if wrapped over multiple lines.
Setting Width="80" on the TextBlock gets the multi-word text to wrap nicely; but screws up the layout of single word items.
What I'd like is something that scales single word elements to fit (like the first two buttons in the top image) and wraps multiple word items prior to scaling to make better use of the available space (similar to the third button in the second example - although there's no need to limit it to only two rows of text if 3 or more would work better).
My XAML used for the examples above is:
<Window x:Class="WpfApplication1.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="525">
<Window.Resources>
<local:MYViewModel x:Key="myVM"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource myVM}}">
<ItemsControl ItemsSource="{Binding ThingsList, Mode= OneWay}"
HorizontalAlignment="Stretch" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Rows="1"
HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button>
<Viewbox>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}" />
</Viewbox>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
And behind it:
public class NamedThing
{
public string Name { get; set; }
}
public class MYViewModel
{
public ObservableCollection<NamedThing> ThingsList { get; set; }
= new ObservableCollection<NamedThing>
{
new NamedThing {Name = "Short"},
new NamedThing {Name = "VeryVeryLongWord"},
new NamedThing {Name = "Several Words in a Row"}
};
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
The problem is that your Viewbox scale whatever is inside it, but the TextBlocks don't know in what they are contained and just grow (width wise) as much as they're allowed to (which is Infinity by default).
A first and quick solution is to set a MaxWidth (100, for example) value to the TextBlock. This way, the multi-word TextBlock will simply react exactly as the single-word ones: it will grow and shrink, but the word wrapping won't change.
The reason MaxWidth works and not Width is logically obvious when you know how Viewbox works: single words TextBlocks are smaller, so their Width is smaller, so they grow much more in a Viewbox than their multi-words counterparts, making them appear in a bigger font.
Expanding on this solution, you can bind MaxWidth to one of the TextBlock's parent's MaxWidth to have a evolving word-wrapping when the window is resized. You can then add a converter that will modify the value (like, divide it by 2) if you feel like the multi-word TextBlock doesn't take enough vertical space.
Code-behind converter:
public class WidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((double)value) / 2.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((double)value) * 2.0;
}
}
XAML resources:
<Window.Resources>
<local:MYViewModel x:Key="myVM" />
<local:WidthConverter x:Key="wc" />
</Window.Resources>
MaxWidth:
<TextBlock TextWrapping="Wrap"
MaxWidth="{Binding ActualWidth, Converter={StaticResource wc}, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Text="{Binding Name}" />
The following example is a bit more complicated and more 'proof-of-concept' than solution; we split the multi-words with a converter to display each single word in a slot of a templated UniformGrid; the behavior seems more natural, but the layout of multi-word strings is kind of gross.
XAML:
<Window x:Class="WpfApplication2.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
Title="MainWindow"
Height="150"
Width="525">
<Window.Resources>
<local:MYViewModel x:Key="myVM" />
<local:ThingConverter x:Key="tc" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource myVM}}">
<ItemsControl ItemsSource="{Binding ThingsList, Mode= OneWay}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3"
Rows="1"
HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button>
<Viewbox>
<ItemsControl ItemsSource="{Binding Converter={StaticResource tc}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextAlignment="Center"
Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Viewbox>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication2
{
public class ThingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new List<string>(((NamedThing)value).Name.Split(' '));
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((List<string>)value).Aggregate((s, ss) => s + " " + ss);
}
}
public class NamedThing
{
public string Name { get; set; }
}
public class MYViewModel
{
public ObservableCollection<NamedThing> ThingsList { get; set; }
public MYViewModel()
{
ThingsList = new ObservableCollection<NamedThing>
{
new NamedThing {Name = "Short"},
new NamedThing {Name = "VeryVeryLongWord"},
new NamedThing {Name = "Several Words in a Row"}
};
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
I'm trying to bind my elements in a datatemplate that is define in dictionary.
Let's make it simple.
I have a simple class
public class A { public string Data {get;set} }
I have a simple view that contains a ListBox, with ItemSources is a list of class A :
<ListBox ItemsSource="{Binding AList}">
The point is, when I define Itemplate in view directly, bind works :
<ListBox.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding Data}" />
<Rectangle Fill="Red" Height="10" Width="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
This works great.
But when I define this ItemTemplate in resource Dictionary, binding doesn't works ?
How can I do that ?
PS : This is a simple example to explain my problem, don't tell me to override toString function to make it works or use classe template, my real case is very more complexe than this.
Thanks for help
Create a new Dictionary1.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="dataTemplate">
<StackPanel>
<TextBlock Text="{Binding Data}" />
<Rectangle Fill="Red" Height="10" Width="10"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
In MainWindow.xaml refer it
<Window.Resources>
<ResourceDictionary Source="Dictionary1.xaml" />
</Window.Resources>
<ListBox Name="lst" ItemTemplate="{StaticResource dataTemplate}"></ListBox>
MainWindow.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
var observable = new ObservableCollection<Test>();
observable.Add(new Test("A"));
observable.Add(new Test("B"));
observable.Add(new Test("C"));
this.lst.ItemsSource = observable;
}
}
public class Test
{
public Test(string dateTime)
{
this.Data = dateTime;
}
public string Data { get; set; }
}
You give your DataTemplate a Key so you can use explicitly define your template and reuse your template. You also need to make sure the ItemsControl is a child of the control which loads the dictionary.
<DataTemplate x:Key="ADataTemplate">
<TextBlock Text="{Binding Data}" />
<Rectangle Fill="Red" Height="10" Width="10"/>
</DataTemplate>
<ListBox ItemsSource="{Binding YourItems}"
ItemTemplate="{StaticResource ADataTemplate}" />
Note: You can use implicit styling on ListBox, however that would apply the same style to all of your ListBoxes.
Declare your data template in the Resources section of the current Window/UserControl etc as follows and then reference via static resource declaration:
<Window.Resources> For example...
<DataTemplate x:Key="MyTemplate">
<TextBlock Text="{Binding Data}" />
<Rectangle Fill="Red" Height="10" Width="10"/>
</DataTemplate>
</Window.Resources>
<ListBox ItemTemplate="{StaticResource MyTemplate}" />
I have this XAML Code (ErdMenuItem.xaml):
<UserControl x:Class="ErdBuilder.ErdMenuItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:erdBuilder="clr-namespace:ErdBuilder"
x:Name="UserControl">
<UserControl.Resources>
<erdBuilder:ByteImageConverter x:Key="imageConverter" />
</UserControl.Resources>
<Image Source="{Binding Converter={StaticResource imageConverter}, ElementName=UserControl, Path=Icon}" />
</UserControl>
If I write:
<Image Source="{Binding Converter={StaticResource imageConverter}, ElementName=UserControl, Path=Icon}" />
Then the Converter will not be executed - I use Breakpoints in the Converter. But if I use this:
<Image Source="{Binding Converter={StaticResource imageConverter}}" />
Then the Converter will be executed. Icon is a Dependency Property of type string. I dont know why the Converter will not be executed as far as I add the DependencyProperty which should bring in the Value which I want to convert. Any Ideas ?
The Icon is here (ErdMenuItem.xaml.cs):
namespace ErdBuilder
{
public partial class ErdMenuItem
{
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(string), typeof(ErdMenuItem), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
public string Icon
{
get { return (string)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
}
}
That's because there's no data in DataContext. The binding expression returns value of the current DataContext. You can set DataContext for Image by using {Binding Converter={StaticResource imageConverter}, Path=Icon} expression or set it for the root element (UserControl in your case). This option is better, as you can use RelativeSource expression to avoid using explicit names.
<UserControl x:Class="ErdBuilder.ErdMenuItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:erdBuilder="clr-namespace:ErdBuilder"
x:Name="UserControl"
DataContext="{Binding RelateSource={RelativeSource Self}}">