I want to add dynamic items with a datatemplate that contains a TextBlock control, but the text of the TextBlock control will be selected from a XAML ResourceDictionary. The staticresource name will be obtained based on the result of the binding value.
How can I do that?
I'm trying something like this, but doesn't works.
<DataTemplate x:Key="languageItemTemplate">
<ContentControl>
<StackPanel>
<TextBlock Text="{StaticResource {Binding ResourceName}}"></TextBlock>
<TextBlock Text="{DynamicResource {Binding ResourceName}}"></TextBlock>
</StackPanel>
</ContentControl>
</DataTemplate>
UPDATE
Thanks to Tobias, the fist option of his answer works. But I need to instance the converter first to get it work. Which one is the best idea to do that?
In the application_startup method and use it for all the application or in the Window.Resources of the window I use the converter?
Maybe a merge of both and do that on the Application.Resources?
thanks for your answer.
private void Application_Startup(object sender, StartupEventArgs e)
{
LoadConverters();
}
private void LoadConverters()
{
foreach (var t in System.Reflection.Assembly.GetExecutingAssembly().GetTypes())
{
if (t.GetInterfaces().Any(i => i.Name == "IValueConverter"))
{
Resources.Add(t.Name, Activator.CreateInstance(t));
}
}
}
OR
<local:BindingResourceConverter x:Key="ResourceConverter"/>
<DataTemplate x:Key="languageItemTemplate">
<ContentControl>
<StackPanel>
<TextBlock Text="{Binding Name, Converter={StaticResource ResourceConverter }}" />
</StackPanel>
</ContentControl>
</DataTemplate>
If the resource is an application level resource you could simply use a converter to convert from the resource name to the actual object like this:
public class BindingResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string resourceKey = value as string;
if (!String.IsNullOrEmpty(resourceKey))
{
var resource = Application.Current.FindResource(resourceKey);
if (resource != null)
{
return resource;
}
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And use it like this:
<TextBlock Text="{Binding ResourceKey, Converter={StaticResource ResourceConverter}}" />
If the resource is in a local scope, we need a reference to the control to search its resources. You can get the resource name and the control by using an attached property:
public class TextBlockHelper
{
public static readonly DependencyProperty TextResourceKeyProperty =
DependencyProperty.RegisterAttached("TextResourceKey", typeof(string),
typeof(TextBlockHelper), new PropertyMetadata(String.Empty, OnTextResourceKeyChanged));
public static string GetTextResourceKey(DependencyObject obj)
{
return (string)obj.GetValue(TextResourceKeyProperty);
}
public static void SetTextResourceKey(DependencyObject obj, string value)
{
obj.SetValue(TextResourceKeyProperty, value);
}
private static void OnTextResourceKeyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
string resourceKey = e.NewValue as string;
if(d is TextBlock tb)
{
var r = tb.TryFindResource(resourceKey);
if (r != null)
{
tb.Text = r.ToString();
}
}
}
}
And you can use it like this:
<Grid>
<Grid.Resources>
<sys:String x:Key="SomeLocalResource">LocalResource</sys:String>
</Grid.Resources>
<TextBlock h:TextBlockHelper.TextResourceKey="{Binding ResourceKey}" />
</Grid>
Related
I have a DependencyProperty in my UserControl with a property changed callback. The property works as expected and the callback works as expected.
public double CurrentFlow
{
get { return (double)GetValue(CurrentFlowProperty); }
set { SetValue(CurrentFlowProperty, value); }
}
public static readonly DependencyProperty CurrentFlowProperty = DependencyProperty.Register("CurrentFlow", typeof(double), typeof(MyUserControl), new PropertyMetadata(0.0, OnCurrentFlowPropertyChanged));
private static void OnCurrentFlowPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("CurrentFlow changed.");
}
However, I have a TextBlock in my UserControl where I want to display CurrentFlow as a formatted string. Currently, I have the Text property of the TextBlock binded to CurrentFlow, and it works, but I'm not getting the format I need. (Too many numbers after the decimal.)
<TextBlock Text="{Binding Path=CurrentFlow, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Ideally, I'd like to have a property named CurrentFlowString that takes the value from CurrentFlow and formats it to what I want. For example: CurrentFlow.ToString("0.00");
What's the best way to go about this with DependencyProperties? I know how to do this with regular properties but I'm kinda stuck here.
Thanks!
If you want to have more flexibility than using StringFormat, you can also use a custom converter. For example,
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double d)
return $"{d:f2}";
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then add it to your UserControl.Resources, and use it in your Binding:
<UserControl.Resources>
<local:MyConverter x:Key="MyConverter" />
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding Path=CurrentFlow, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource MyConverter}}" />
</Grid>
Solution 2:
Based on your comment below, here's an alternative solution. First, create a new dependency property; for example, FormattedCurrentFlow:
public static readonly DependencyProperty FormattedCurrentFlowProperty = DependencyProperty.Register(
"FormattedCurrentFlow", typeof(string), typeof(MyControl), new PropertyMetadata(default(string)));
public string FormattedCurrentFlow
{
get { return (string)GetValue(FormattedCurrentFlowProperty); }
set { SetValue(FormattedCurrentFlowProperty, value); }
}
Since you already have a method to handle changes in CurrentFlow, update the new FormattedCurrentFlow when CurrentFlow changes:
private static void OnCurrentFlowPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var myControl = (MyControl)source;
myControl.FormattedCurrentFlow = $"{myControl.CurrentFlow:f2}";
}
The TextBox in the UserControl can now bind to FormattedCurrentFlow:
<TextBlock Text="{Binding Path=FormattedCurrentFlow, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Is it somehow possible to set a maximum scale of the content in a ViewBox? If I have this code for instance, I want to ensure that the text is not scaled more than 200%
<Grid>
<Viewbox>
<TextBox Text="Hello world" />
</Viewbox>
</Grid>
For future readers, I've written an even more elegant approach based on user2250152's answer that uses an attached property:
public static class ViewboxExtensions
{
public static readonly DependencyProperty MaxZoomFactorProperty =
DependencyProperty.Register("MaxZoomFactor", typeof(double), typeof(ViewboxExtensions), new PropertyMetadata(1.0, OnMaxZoomFactorChanged));
private static void OnMaxZoomFactorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var viewbox = d as Viewbox;
if (viewbox == null)
return;
viewbox.Loaded += OnLoaded;
}
private static void OnLoaded(object sender, RoutedEventArgs e)
{
var viewbox = sender as Viewbox;
var child = viewbox?.Child as FrameworkElement;
if (child == null)
return;
child.SizeChanged += (o, args) => CalculateMaxSize(viewbox);
CalculateMaxSize(viewbox);
}
private static void CalculateMaxSize(Viewbox viewbox)
{
var child = viewbox.Child as FrameworkElement;
if (child == null)
return;
viewbox.MaxWidth = child.ActualWidth * GetMaxZoomFactor(viewbox);
viewbox.MaxHeight = child.ActualHeight * GetMaxZoomFactor(viewbox);
}
public static void SetMaxZoomFactor(DependencyObject d, double value)
{
d.SetValue(MaxZoomFactorProperty, value);
}
public static double GetMaxZoomFactor(DependencyObject d)
{
return (double)d.GetValue(MaxZoomFactorProperty);
}
}
The attached property can be added to a viewbox like this:
<Viewbox extensions:ViewboxExtensions.MaxZoomFactor="2.0">...</Viewbox>
You need to set MaxWidth and MaxHeight on ViewBox:
<Grid>
<Viewbox x:Name="MyViewBox">
<TextBox x:Name="MyTextBox" Text="Hello world" />
</Viewbox>
</Grid>
and in code behind:
MyViewBox.MaxWidth = MyTextBox.ActualWidth * 2d;
MyViewBox.MaxHeight = MyTextBox.ActualHeight * 2d;
Or maybe better solution with converter:
public class ViewBoxMaxWidthOrHeightConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double textBoxWidthOrHeight = (double) value;
return textBoxWidthOrHeight*2d;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
and XAML:
<local:ViewBoxMaxWidthOrHeightConverter x:Key="ViewBoxMaxWidthOrHeightConverter"/>
<Grid>
<Viewbox x:Name="MyViewBox"
MaxWidth="{Binding ElementName=MyTextBox, Path=ActualWidth, Converter={StaticResource ViewBoxMaxWidthOrHeightConverter}, Mode=OneWay}"
MaxHeight="{Binding ElementName=MyTextBox, Path=ActualHeight, Converter={StaticResource ViewBoxMaxWidthOrHeightConverter}, Mode=OneWay}">
<TextBox x:Name="MyTextBox" Text="Hello world" />
</Viewbox>
</Grid>
I have created an attached property to set the visibility of a UIElement based on a particular enum value. This works fine. However, I need to extend this so that the visibility can be overriden based on the "status" of the sender.
How can I achieve this? I had thought that I could create another attached property which the first attached property could reference, however I need to be able to bind a value to the second attached property rather than just set to an enum value.
EDIT
Below is an example of my problem:
<Window x:Class="AttachedProperty.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:attachedProperty="clr-namespace:AttachedProperty"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TextBlock Text="Button should be enabled?"/>
<CheckBox IsChecked="{Binding Path=CanClick}"/>
<Button Content="Click Me" IsEnabled="{Binding Path=CanClick}"/>
<Button Content="Manager Only Click" attachedProperty:SecurityBehavior.IsEnabledRole="Mgr"/>
</StackPanel>
</Grid>
The first button's enabled property is just controlled using the checkbox.
The second button's enabled property is controlled by an attachedProperty that determines if you are in the correct security group:
public class SecurityBehavior
{
public static readonly DependencyProperty IsEnabledRoleProperty = DependencyProperty.RegisterAttached(
"IsEnabledRole", typeof (string), typeof (SecurityBehavior), new UIPropertyMetadata(OnIsEnabledRoleChanged));
private static void OnIsEnabledRoleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// In here have some logic that determines if the current user is authorised
// Principal.IsInRole(e.NewValue.ToString() ? true : false;
sender.SetValue(UIElement.IsEnabledProperty, true);
}
public static void SetIsEnabledRole(DependencyObject element, string value)
{
element.SetValue(IsEnabledRoleProperty, value);
}
public static string GetIsEnabledRole(DependencyObject element)
{
return (string) element.GetValue(IsEnabledRoleProperty);
}
}
When run both buttons are enabled - the 1st because the checkbox is checked and the second because I am a manager. When I uncheck the checkbox the 1st button is disabled and I want my attached property to be able to only enable if in the correct security group AND the checkbox is checked.
How can I change by sample so that I can get have the behavior that sets the IsEnabled based on 2 possible inputs?
Not sure why you're after attached properties for this. Based on your requirement you could pull this off with a simple IValueConverter and a Binding for the Visibility of the final control.
So say we have an enum:
public enum MyEnum {
StateOne,
StateTwo
}
and a CheckBox such as:
<CheckBox x:Name="chkBox"
Content="Check Me!!!" />
Now if we want some Button's Visibility to only be visible when enum is StateOne and the checkbox is checked,
we could just have a converter such as:
public class MyConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var checkBoxIsChecked = (bool)value;
var givenEnum = (MyEnum)parameter;
return checkBoxIsChecked && givenEnum == MyEnum.StateOne ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
and in xaml for the Button's:
<StackPanel>
<StackPanel.Resources>
<local:MyConverter x:Key="MyConverter" />
</StackPanel.Resources>
<CheckBox x:Name="chkBox"
Content="Check Me!!!" />
<Button Content="Button One"
Visibility="{Binding ElementName=chkBox,
Path=IsChecked,
Converter={StaticResource MyConverter},
ConverterParameter={x:Static local:MyEnum.StateOne}}" />
<Button Content="Button Two"
Visibility="{Binding ElementName=chkBox,
Path=IsChecked,
Converter={StaticResource MyConverter},
ConverterParameter={x:Static local:MyEnum.StateTwo}}" />
</StackPanel>
With this, "Button One" will become visible when the checkbox is checked, however button two will not as the ConverterParameter passed in for button two is StateTwo.
If it's the IsEnabled state you want to control of the Button, just switch the binding to that property and in the converter just return true or false accordingly instead of Visibility.Visible
Even if you choose to provide the enum value not static and dynamic, you could just make the Binding a MultiBinding and switch the IValueConverter to an IMultiValueConverter
Update:
If for whatever reason you have to go down the route of attached properties, then in the property changed callback of each attached property get the other properties value from the sender and perform your logic accordingly.
public class SecurityBehavior {
public static readonly DependencyProperty IsEnabledRoleProperty = DependencyProperty.RegisterAttached(
"IsEnabledRole",
typeof(string),
typeof(SecurityBehavior),
new UIPropertyMetadata(OnIsEnabledRoleChanged));
public static readonly DependencyProperty IsEnabled2RoleProperty = DependencyProperty.RegisterAttached(
"IsEnabled2Role",
typeof(bool),
typeof(SecurityBehavior),
new UIPropertyMetadata(OnIsEnabled2RoleChanged));
private static void OnIsEnabledRoleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
HandleAttachedPropertyUpdate(sender, (string)e.NewValue, GetIsEnabled2Role(sender));
}
private static void OnIsEnabled2RoleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
HandleAttachedPropertyUpdate(sender, GetIsEnabledRole(sender), (bool)e.NewValue);
}
private static void HandleAttachedPropertyUpdate(DependencyObject sender, string isEnabledRole, bool isEnabled2Role) {
sender.SetValue(UIElement.IsEnabledProperty, isEnabledRole == "Mgr" && isEnabled2Role);
}
public static void SetIsEnabledRole(DependencyObject element, string value) {
element.SetValue(IsEnabledRoleProperty, value);
}
public static string GetIsEnabledRole(DependencyObject element) {
return (string)element.GetValue(IsEnabledRoleProperty);
}
public static void SetIsEnabled2Role(DependencyObject element, bool value) {
element.SetValue(IsEnabled2RoleProperty, value);
}
public static bool GetIsEnabled2Role(DependencyObject element) {
return (bool)element.GetValue(IsEnabled2RoleProperty);
}
}
and xaml:
<StackPanel>
<CheckBox x:Name="chkBox"
Content="Check Me!!!" />
<Button Content="Button One"
local:SecurityBehavior.IsEnabled2Role="{Binding ElementName=chkBox,
Path=IsChecked}"
local:SecurityBehavior.IsEnabledRole="Mgr" />
<Button Content="Button Two"
local:SecurityBehavior.IsEnabled2Role="{Binding ElementName=chkBox,
Path=IsChecked}"
local:SecurityBehavior.IsEnabledRole="NotMgr" />
</StackPanel>
Say I've got some TextBlocks on my UI, something like so:
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding DessertIndex}" />
<TextBlock Text="{Binding Food[2]}" />
<TextBlock Text="{Binding Food[{Binding DessertIndex}]}" />
</StackPanel>
and in my code behind I've got something like this:
public partial class MainWindow : Window
{
public int DessertIndex
{
get { return 2; }
}
public object[] Food
{
get
{
return new object[]{"liver", "spam", "cake", "garlic" };
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
The first two TextBlocks display fine for me, displaying 2 and 'cake' respectively. The third one doesn't accomplish what I'd like, namely use the DessertIndex property to index into that array and also display 'cake'. I did a little searching here on SO for a similar question but didn't find one. Ultimately, I don't want to specify values like 2 in my .xaml file and would like to rely upon a property instead for indexing into that array. Is this possible? If so, what am I doing wrong here?
EDIT:
So what I more closely have is a situation where the data is a List of these object[] and I'm using the above StackPanel as part of a DataTemplate for a ListBox. So the idea, as Mark Heath suggests below, of using a property that dereferences the array doesn't seem to work as I'd want. Ideas?
Another alternative is to use MultiBinding with a converter:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<local:FoodIndexConverter x:Key="foodIndexConverter" />
</StackPanel.Resources>
<TextBlock Text="{Binding DessertIndex}" />
<TextBlock Text="{Binding Food[2]}" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource foodIndexConverter}">
<Binding Path="DessertIndex" />
<Binding Path="Food"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Window>
Then in the code-behind, the converter is defined something like this:
namespace WpfApplication1
{
public class FoodIndexConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values == null || values.Length != 2)
return null;
int? idx = values[0] as int?;
object[] food = values[1] as object[];
if (!idx.HasValue || food == null)
return null;
return food[idx.Value];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
if you are going to the trouble of having a DesertIndex property on your DataContext, why not a property that dereferences the Food array with DesertIndex:
public object SelectedFood
{
get { return Food[DessertIndex]; }
}
public int DessertIndex
{
get { return 2; }
}
public object[] Food
{
get
{
return new object[]{"liver", "spam", "cake", "garlic" };
}
}
then you can bind directly to that:
<TextBlock Text="{Binding SelectedFood}" />
This is essentially the "MVVM" approach: make the datacontext object have properties that are just right for binding to.
Just To add on the great answer by Colin Thomsen.
You could also use C# dynamic keyword to make this solution work with pretty much every container type. Or even bind to multidimensional containers "{Binding Food[{Binding DessertIndex1}][{Binding DessertIndex2}]}"
public class ContainerDoubleAccessConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
dynamic idx1 = values[0];
dynamic idx2 = values[1];
dynamic container = values[2];
return container[idx1][idx2];
}
catch (System.Exception err)
{
DebugTrace.Trace("bad conversion " + err.Message);
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
does anyone know if there is a simple way to bind a textblock to a List.
What I've done so far is create a listview and bind it to the List and then I have a template within the listview that uses a single textblock.
what I'd really like to do is just bind the List to a textblock and have it display all the lines.
In Winforms there was a "Lines" property that I could just throw the List into, but I'm not seeing it on the WPF textblock, or TextBox.
Any ideas?
did I miss something simple?
Here's the code
<UserControl x:Class="QSTClient.Infrastructure.Library.Views.WorkItemLogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="400">
<StackPanel>
<ListView ItemsSource="{Binding Path=Logs}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Log Message">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
and the WorkItem Class
public class WorkItem
{
public string Name { get; set; }
public string Description { get; set; }
public string CurrentLog { get; private set; }
public string CurrentStatus { get; private set; }
public WorkItemStatus Status { get; set; }
public ThreadSafeObservableCollection<string> Logs{get;private set;}
I'm using Prism to create the control and put it into a WindowRegion
WorkItemLogView newView = container.Resolve<WorkItemLogView>();
newView.DataContext = workItem;
regionManager.Regions["ShellWindowRegion"].Add(newView);
thanks
Convert your List to a single string with "\r\n" as the delimiter in between. and bind that to the TextBlock. Make sure that the TextBlock is not restricted with its height , so that it can grow based on the number of lines.
I would implement this as a Value Converter to XAML Binding which converts a List of strings to a single string with new line added in between
<TextBlock Text="{Binding Path=Logs,Converter={StaticResource ListToStringConverter}}"/>
The ListToStringConverter would look like this:
[ValueConversion(typeof(List<string>), typeof(string))]
public class ListToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(string))
throw new InvalidOperationException("The target must be a String");
return String.Join(", ", ((List<string>)value).ToArray());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
if you use the converter it works for the first time perfect,
but if one or more logging comes to the logging list, there is no update on your binding, because the converter works only at the first time.
all controls that are no item controls doesn't subscribe to the listchanged event!
here is a little code for this scenario
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace BindListToTextBlock
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private WorkItem workItem;
public MainWindow() {
this.WorkItems = new ObservableCollection<WorkItem>();
this.DataContext = this;
this.InitializeComponent();
}
public class WorkItem
{
public WorkItem() {
this.Logs = new ObservableCollection<string>();
}
public string Name { get; set; }
public ObservableCollection<string> Logs { get; private set; }
}
public ObservableCollection<WorkItem> WorkItems { get; set; }
private void Button_Click(object sender, RoutedEventArgs e) {
this.workItem = new WorkItem() {Name = string.Format("new item at {0}", DateTime.Now)};
this.workItem.Logs.Add("first log");
this.WorkItems.Add(this.workItem);
}
private void Button_Click_1(object sender, RoutedEventArgs e) {
if (this.workItem != null) {
this.workItem.Logs.Add(string.Format("more log {0}", DateTime.Now));
}
}
}
}
the xaml
<Window x:Class="BindListToTextBlock.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:BindListToTextBlock="clr-namespace:BindListToTextBlock"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.Resources>
<BindListToTextBlock:ListToStringConverter x:Key="ListToStringConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0"
Content="Add item..."
Click="Button_Click" />
<Button Grid.Row="1"
Content="Add some log to last item"
Click="Button_Click_1" />
<ListView Grid.Row="2"
ItemsSource="{Binding Path=WorkItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Log Message">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Logs, Converter={StaticResource ListToStringConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
the converter
using System;
using System.Collections;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace BindListToTextBlock
{
public class ListToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is IEnumerable) {
return string.Join(Environment.NewLine, ((IEnumerable)value).OfType<string>().ToArray());
}
return "no messages yet";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return DependencyProperty.UnsetValue;
}
}
}
EDIT
here is a quick solution for the update propblem (this can be also made with a attached property)
public class CustomTextBlock : TextBlock, INotifyPropertyChanged
{
public static readonly DependencyProperty ListToBindProperty =
DependencyProperty.Register("ListToBind", typeof(IBindingList), typeof(CustomTextBlock), new PropertyMetadata(null, ListToBindPropertyChangedCallback));
private static void ListToBindPropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var customTextBlock = o as CustomTextBlock;
if (customTextBlock != null && e.NewValue != e.OldValue) {
var oldList = e.OldValue as IBindingList;
if (oldList != null) {
oldList.ListChanged -= customTextBlock.BindingListChanged;
}
var newList = e.NewValue as IBindingList;
if (newList != null) {
newList.ListChanged += customTextBlock.BindingListChanged;
}
}
}
private void BindingListChanged(object sender, ListChangedEventArgs e)
{
this.RaisePropertyChanged("ListToBind");
}
public IBindingList ListToBind
{
get { return (IBindingList)this.GetValue(ListToBindProperty); }
set { this.SetValue(ListToBindProperty, value); }
}
private void RaisePropertyChanged(string propName)
{
var eh = this.PropertyChanged;
if (eh != null) {
eh(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
here is the usage for the CustomTextBlock (not tested)
<TextBlock Text="{Binding Path=ListToBind, RelativeSource=Self, Converter={StaticResource ListToStringConverter}}"
ListToBind={Binding Path=Logs} />
#Fueled hope this helps
I'll shamelessly post a link to my answer of a very similar question: Binding ObservableCollection<> to a TextBox.
Like punker76 said, if you bind your Text to a collection it will update when you set the collection, but not when the collection changes. This link demonstrates an alternative to punker76's solution (the trick is to multi-bind to the collection's count too).
For concat collection of objects :
/// <summary>Convertisseur pour concaténer des objets.</summary>
[ValueConversion(typeof(IEnumerable<object>), typeof(object))]
public class ConvListToString : IValueConverter {
/// <summary>Convertisseur pour le Get.</summary>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return String.Join(", ", ((IEnumerable<object>)value).ToArray());
}
/// <summary>Convertisseur inverse, pour le Set (Binding).</summary>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
Juste think to overide the ToString() of your object.