Set maximum scale of ViewBox - wpf

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>

Related

How can I set a Static/Dynamic resource from a binding value?

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>

Telerik Tile Background binding

I'm trying to bind the background of a Tile in a RadTileList, Tiles are created from a collection on the Itemsource of the RadTileList, so far I've tried changing the background on the border container in the Datatemplate, but it seems that the Tile background property is wining over that.
In the code above , I've tried to set the ItemContainerStyle and set the binding for the background, but nothing changes, I hope someone could help me.
Note: The color of the background is a string var so im using a converter, wich I tested independently
<telerik:RadTileList ItemsSource="{Binding Modulo.Modulos_Detail}" TileReorderMode="None"
ScrollViewer.HorizontalScrollBarVisibility="Visible">
<telerik:RadTileList.ItemContainerStyle>
<Style >
<Setter Property="telerik:Tile.TileType" Value="Quadruple" />
<Setter Property="telerik:Tile.Background" Value="{Binding .Color, Converter={StaticResource strHexColorConverter}}" />
</Style>
</telerik:RadTileList.ItemContainerStyle>
<telerik:RadTileList.ItemTemplate>
<DataTemplate>
<Border >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Grid.Row="0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Source="{Binding .Imagenes.Imagen}" Stretch="Uniform"></Image>
<TextBlock Grid.Row="1" Padding="5"
Text="{Binding Descripcion}" FontSize="20" TextWrapping="Wrap"
VerticalAlignment="Bottom"/>
<!--<Image Source="{Binding .LockViewImage, Converter={StaticResource imgBitmapImageConverter}}" />-->
</Grid>
</Border>
</DataTemplate>
</telerik:RadTileList.ItemTemplate>
Create a seperate style with the Tile as target type. Then you only need to set the Background property. Optionally you could give the style a name and set it explicitly on the RadTileList.
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="telerik:Tile" BasedOn="{StaticResource TileStyle}">
<Setter Property="Background" Value="{StaticResource OmegaMainColorBrush}" />
</Style>
</ResourceDictionary>
</UserControl.Resources>
In my opinion you can use a Boolean Attached property in the telerik:Tile control style. If that property is True then you create the binding in code behind. The only one thing you should care about is that the Content of the Tile will hold the the object that the .Color is defined there.
Here is the code.
Style (put this into the Resource part)
<Style TargetType="telerik:Tile" BasedOn="{StaticResource {x:Type telerik:Tile}}">
<Setter Property="flowConfiguration:TileAttachedProperties.IsTyleTypeBound" Value="True"/>
</Setter>
The attached property code(with converters)
public class TileAttachedProperties
{
public static readonly DependencyProperty IsTyleTypeBoundProperty = DependencyProperty.RegisterAttached(
"IsTyleTypeBound",
typeof (bool),
typeof (TileAttachedProperties),
new PropertyMetadata(default(bool), IsTyleBoundPropertyChangedCallback));
private static void IsTyleBoundPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var tile = sender as Tile;
var isBound = (bool) args.NewValue;
if(tile == null || isBound == false) return;
tile.Loaded += TileOnLoaded;
}
private static void TileOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var tile = sender as Tile;
if (tile == null) return;
tile.Loaded -= TileOnLoaded;
var tileContent = tile.Content;
if (tileContent == null || tileContent is ItemTypeWrapper == false) return;
//here we create binding to define if the type of the Tile(single or double)
var tileTypeBinding = new Binding("IsDouble");
tileTypeBinding.Source = tileContent;
tileTypeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
tileTypeBinding.Converter = new Bool2TileTypeConverter();
tile.SetBinding(Tile.TileTypeProperty, tileTypeBinding);
//here we create binding to define the background of the tile
var tileBackgroundBinding = new Binding("IsDouble");
tileBackgroundBinding.Source = tileContent;
tileBackgroundBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
tileBackgroundBinding.Converter = new Bool2BackgroundConverter();
tile.SetBinding(Tile.BackgroundProperty, tileBackgroundBinding);
}
public static void SetIsTyleTypeBound(DependencyObject element, bool value)
{
element.SetValue(IsTyleTypeBoundProperty, value);
}
public static bool GetIsTyleTypeBound(DependencyObject element)
{
return (bool) element.GetValue(IsTyleTypeBoundProperty);
}
}
internal class Bool2BackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isDouble = (bool)value;
return isDouble ? new SolidColorBrush(Color.FromArgb(255, 255, 0, 255)) : new SolidColorBrush(Color.FromArgb(255,0, 255, 255));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
internal class Bool2TileTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isDouble = (bool) value;
return isDouble ? TileType.Double : TileType.Single;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
How it looks like:
Regards,

Setting button'sproperty IsEnabled based if listbox has content in it with BoolConverter in Silverlight?

I am trying to make a button Enabled only if a listbox has at least one item in it. I am adding listboxitems by selecting them from AutoCompleteBox list. I tried to bind isEnabled property to the listbox and use BoolConverter to validate if listbox has content.
<Button x:Name="DisabledButton" Click="RemoveButton_Click" Content="Disabled Button" IsEnabled="{Binding ItemsSource, ElementName=EntitiesListBox,Converter={StaticResource CountToBooleanConverter}}" />
I am missing something. I am wondering if someone can tell me what is wrong. Any ideas are highly appreciated!
Silverlight XAML:
<UserControl.Resources>
<local:BoolToOppositeBoolConverter x:Key="CountToBooleanConverter" />
<local:CountGreaterThanZeroConverter x:Key="CountGreaterThanZeroConverter" />
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Background="White" Width="150">
<TextBlock Text="{Binding ElementName=MyAutoCompleteBox, Path=SelectedItem, TargetNullValue='No item selected', StringFormat='Selected Item: {0}'}" />
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox" IsTextCompletionEnabled="True" ItemsSource="{Binding Items}" />
<Button x:Name="AddButton" Click="AddButton_Click" Content="AddButton" />
<Button x:Name="RemoveButton" Click="RemoveButton_Click" Content="RemoveButton" />
<Button x:Name="DisabledButton" Click="RemoveButton_Click" Content="Disabled Button" IsEnabled="{Binding ItemsSource, ElementName=ListBox,Converter={StaticResource CountToBooleanConverter}}" />
<Button x:Name="DisabledButton2" Click="RemoveButton_Click" Content="Disabled Button" IsEnabled="{Binding ItemsSource.Count, ElementName=ListBox, Converter={StaticResource CountGreaterThanZeroConverter}}" />
</StackPanel>
Code behind:
public partial class MainPage : UserControl
{
string currentItemText;
public ObservableCollection<string> Items
{
get;
private set;
}
public MainPage()
{
InitializeComponent();
Items = new ObservableCollection<string>();
Items.Add("One");
Items.Add("Two");
Items.Add("Three");
Items.Add("Four");
DataContext = this;
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
currentItemText = MyAutoCompleteBox.SelectedItem.ToString();
ListBox.Items.Add(currentItemText);
ApplyDataBinding();
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
if (ListBox.SelectedItems != null)
{
int count = ListBox.SelectedItems.Count - 1;
for (int i = count; i >= 0; i--)
{
ListBox.Items.Remove(ListBox.SelectedItems[i]);
}
ApplyDataBinding();
}
}
private void ApplyDataBinding()
{
MyAutoCompleteBox.ItemsSource = null;
}
}
public class CountGreaterThanZeroConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value > 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Why not just bind to the count of the itemssource and use an observable collection?
<Button x:Name="DisabledButton" Click="RemoveButton_Click" Content="Disabled Button" IsEnabled="{Binding ItemsSource.Count, ElementName=ListBox,Converter={StaticResource CountGreaterThanZeroConverter}}" />
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (int)value > 0;
}
And in your viewmodel
public ObservableCollection<string> Items
{
get;
private set;
}

WPF: Can i set the width of an element by percentages?

Say I have 2 buttons in an Element and I want to set the 2 elements to always fill up 1/2 width of its containing element each, can i do that?
UPDATE
why cant i do something like
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Button Content="Click me" Command="{Binding ClickCommand}" Width="1*" />
<Button Content="Exit" Command="{Binding CloseCommand}" Width="1*" />
</StackPanel>
why doesnt the 1* work in this context? i get the error
Cannot convert "1*"
You can use a Grid with two columns for this.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0">Button1</Button>
<Button Grid.Column="1">Button2</Button>
</Grid>
Notice the use of star(*) in the ColumnDefinition.Width property. This means that both columns will take up the same amount of space. So in the example above, each button will each occupy 1/2 of the available space of the containing Grid. So if you make one Width to be equal to 2*, that column will take up twice the amount of space as the other column. Hope this makes sense.
I created a ContentControl that allows me wrap content to add a dynamic percentage width/height.
/// <summary>
/// This control has a dynamic/percentage width/height
/// </summary>
public class FluentPanel : ContentControl, IValueConverter
{
#region Dependencie Properties
public static readonly DependencyProperty WidthPercentageProperty =
DependencyProperty.Register("WidthPercentage", typeof(int), typeof(FluentPanel), new PropertyMetadata(-1, WidthPercentagePropertyChangedCallback));
private static void WidthPercentagePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
((FluentPanel)dependencyObject).OnWidthPercentageChange();
}
public int WidthPercentage
{
get { return (int)GetValue(WidthPercentageProperty); }
set { SetValue(WidthPercentageProperty, value); }
}
public static readonly DependencyProperty HeightPercentageProperty =
DependencyProperty.Register("HeightPercentage", typeof(int), typeof(FluentPanel), new PropertyMetadata(-1, HeightPercentagePropertyChangedCallback));
private static void HeightPercentagePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
((FluentPanel)dependencyObject).OnHeightPercentageChanged();
}
public int HeightPercentage
{
get { return (int)GetValue(HeightPercentageProperty); }
set { SetValue(HeightPercentageProperty, value); }
}
#endregion
#region Methods
private void OnWidthPercentageChange()
{
if (WidthPercentage == -1)
{
ClearValue(WidthProperty);
}
else
{
SetBinding(WidthProperty, new Binding("ActualWidth") { Source = Parent, Converter = this, ConverterParameter = true });
}
}
private void OnHeightPercentageChanged()
{
if (HeightPercentage == -1)
{
ClearValue(HeightProperty);
}
else
{
SetBinding(HeightProperty, new Binding("ActualHeight") { Source = Parent, Converter = this, ConverterParameter = false });
}
}
#endregion
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)parameter)
{
// width
return (double)value * (WidthPercentage * .01);
}
else
{
// height
return (double)value * (HeightPercentage * .01);
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}

WPF textblock binding with List<string>

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.

Resources