In my View Model (VM) I have an ObservableCollection of items. In my view I am binding to the collection. I have created a few user controls that have a dependency property that i am binding to called STCode. So for example a "Tag" object will have a "Name" property of type String and a "value" property of type integer.
In my ViewModel
Constructor
Tags.Add(new Tag("Tag1",111));
Tags.Add(new Tag("Tag2",222));
Tags.Add(new Tag("Tag3",333));
Tags.Add(new Tag("Tag4",444));
public ObservableCollection<Tag> Tags
{
get
{
return _TagList;
}
set
{
if (value != _TagList)
{
_TagList = value;
}
}
}
In my View
<my:UserControl1 x:Name="control1" Margin="12,89,0,0" HorizontalAlignment="Left" Width="257" Height="249" VerticalAlignment="Top" STCode="{Binding Path=Value}"/>
This will bind to the First items value property in the ObservableCollection (Showing "Tag1" value). Is there anyway that I can get a specific "tag" object from the observableCollection by specifying the string Name property? So basically if I had 3 instances of my usercontrol in the view, on each control I would like to specify the "Name" property of the Tag object as a string in XAML, and in return bind that specific control to that specific tags integer "Value" property?
I hope this makes sense
Model
public class Tag : ModelBase
{
private int _value;
public string Tagname { get; set; }
public int Value
{
get
{
return _value;
}
set
{
_value = value;
NotifyPropertyChanged("Value");
}
}
}
ModelBase
public class ModelBase :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Since your UserControls are bound to the Collection itself and not the Item on the collection (the converter does this job internally) you must call PropertyChanged on the whole Collection when you want to refresh the bindings on your usercontrols.
Edit: Full solution
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection<Tag> Tags { get; private set; }
public MainWindowViewModel()
{
Tags = new ObservableCollection<Tag>();
Tags.Add(new Tag("Tag1", 111));
Tags.Add(new Tag("Tag2", 222));
Tags.Add(new Tag("Tag3", 333));
Tags.Add(new Tag("Tag4", 444));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public void ChangeRandomTag()
{
var rand = new Random();
var tag = Tags[rand.Next(0, Tags.Count - 1)];
tag.Value = rand.Next(0, 1000);
OnPropertyChanged("Tags");
}
}
View XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<wpfApplication1:MyConverter x:Key="MyConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Tags}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="1"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Orientation="Vertical">
<Button x:Name="buttonChangeRandomTag"
Click="ButtonChangeRandomTag_OnClick"
Content="Change Random Tag Value" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag1}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag2}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag3}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag4}" />
</StackPanel>
</Grid>
View Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent();
}
private void ButtonChangeRandomTag_OnClick(object sender, RoutedEventArgs e)
{
(DataContext as MainWindowViewModel).ChangeRandomTag();
}
}
Converter:
[ValueConversion(typeof(ObservableCollection<Tag>), typeof(int))]
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = value as ObservableCollection<Tag>;
var key = parameter as string;
if (collection == null || parameter == null)
return 0;
var result = collection.FirstOrDefault(item => item.Name.Equals(key));
if (result == null)
return 0;
return result.Value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Tag Class:
public class Tag : INotifyPropertyChanged
{
private string name;
private int value;
public string Name
{
get { return name; }
set
{
if (value == name) return;
name = value;
OnPropertyChanged("Name");
}
}
public int Value
{
get { return value; }
set
{
if (value == this.value) return;
this.value = value;
OnPropertyChanged("Value");
}
}
public Tag(string name, int value)
{
Value = value;
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You could use a ValueConverter to do that for you:
[ValueConversion(typeof(string), typeof(string))]
public class StringToTagPropertyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value != typeof(ObservableCollection<Tag>)) return
DependencyProperty.UnsetValue;
if (parameter as string == null) return DependencyProperty.UnsetValue;
ObservableCollection<Tag> tagObject = (ObservableCollection<Tag>)value;
string returnValue = tagObject.Where(t => t.Name.ToLower() ==
parameter.ToString().ToLower()).FirstOrDefault();
return returnValue ?? DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
You would use it like this:
<my:UserControl1 x:Name="control1" Margin="12,89,0,0" HorizontalAlignment="Left"
Width="257" Height="249" VerticalAlignment="Top" STCode="{Binding Tags,
Converter={StaticResource StringToTagPropertyConverter},
ConverterParameter="Name"}" />
By changing the value of the ConverterParameter, you can get the ValueConverter to return different properties of your 'tag object'. I am assuming that you know how to add a value converter in XAML.
Related
I'm trying to bind the LineSeries Color to my viewmodel. It has no effect.
What is wrong?
The Datacontext is set in the MainWindow. That works because I get the data from Chartpoints.
<UserControl x:Class="MyLayerThicknessApp.UserControls.RefAufnehmenEdit"
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:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:local="clr-namespace:MyLayerThicknessApp.UserControls"
mc:Ignorable="d"
d:DesignHeight="850" d:DesignWidth="1000">
<Grid>
<!--Chart-->
<oxy:Plot
LegendOrientation="Vertical"
LegendPlacement="Outside"
LegendPosition="RightTop" Grid.Row="1" IsEnabled="False">
<oxy:Plot.Axes>
<oxy:LinearAxis Position="Bottom" Minimum="400" Maximum="850" MajorGridlineStyle="None" MinorGridlineStyle="None" Title="Wellenlänge [nm]" />
<oxy:LinearAxis Position="Left" Minimum="0" Maximum="4000" Title="Intensität" MajorStep="1000" MajorGridlineStyle="Automatic" MinorGridlineStyle="None"/>
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:LineSeries
DataFieldY="YValue" DataFieldX="XValue" ItemsSource="{Binding ChartPoints}" Color="{Binding LinieColor}" LineStyle="Solid" />
</oxy:Plot.Series>
</oxy:Plot>
</Grid>
</UserControl>
// Mainwindow
<Window x:Class="MyLayerThicknessApp.Views.RecipesView"
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:MyLayerThicknessApp.Views"
xmlns:vm="clr-namespace:MyLayerThicknessApp.ViewModels"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:MyControls="clr-namespace:MyLayerThicknessApp.UserControls"
mc:Ignorable="d"
Title="RezepteWindow" Height="850" Width="1200" WindowStartupLocation="CenterOwner" Style="{StaticResource Window}" IsEnabled="{Binding MainWindowIsEnable}" >
<Window.DataContext>
<vm:RecipesViewModel/>
</Window.DataContext>
<Grid Background="Transparent">
<MyControls:RefAufnehmenEdit Visibility="{Binding VisEditRefAufnehmen, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</Window>
My ViewModel. I also try with Color and Brushes. Nothing works.
The INotifyPropertyChanged is in the MainViewMode. This works for sure.
public class RecipesViewModel : MainViewModel
{
private bool visEditRefAufnehmen;
public bool VisEditRefAufnehmen
{
get { return visEditRefAufnehmen; }
set
{
if (visEditRefAufnehmen != value)
{
visEditRefAufnehmen = value;
OnPropertyChanged("VisEditRefAufnehmen"); // To notify when the property is changed
}
}
}
private OxyColor linieColor = OxyColor.Parse("255,0,0");
public OxyColor LinieColor
{
get { return linieColor; }
set
{
if (linieColor != value)
{
linieColor = value;
OnPropertyChanged("LinieColor"); // To notify when the property is changed
}
}
}
private List<DataPoint> currentchartPoints;
public List<DataPoint> CurrentchartPoints
{
get { return currentchartPoints; }
set
{
if (value != currentchartPoints)
{
currentchartPoints = value;
OnPropertyChanged("CurrentchartPoints");
}
}
}
private void spektrumAufnehmen()
{
if (spektrometer == null)
{
spektrometer = MySpektrometer.getInstanz();
spektrometer.SetINtegrationTime(10);
}
Task.Run(() =>
{
while (VisEditRefAufnehmen)
{
double[] intensity = MySpektrometer.Wrapper.getSpectrum(0);
double[] wave = MySpektrometer.Wrapper.getWavelengths(0);
wave = MyFilter.convertBigArrayToSmall(wave, intensity, Brechungsindex.WAVELENGTHAREA);
ChartPoints = DoubleArrayToChartPoints(wave);
}
});
}
}
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
The property LineSeries.Color expects a type of System.Windows.Media.Color, where as, the the property in your ViewModel, LinieColor, is of type OxyColor.
From OxyPlot Source
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(
"Color", typeof(Color), typeof(Series), new PropertyMetadata(MoreColors.Automatic, AppearanceChanged));
From your ViewModel
public OxyColor LinieColor
You could resolve the binding issue by using a converter that converts OxyColor to Color. A simplest converter for the purpose could be following.
public class OxyColorToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is OxyColor color)
return color.ToColor();
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in your XAML
<oxy:LineSeries DataFieldY="YValue" DataFieldX="XValue" ItemsSource="{Binding ChartPoints}" Color="{Binding LinieColor,Converter={StaticResource OxyColorToColorConverter}}" LineStyle="Solid" />
In this silverlight grid cell, the display value does not update when the edit value is updated, any ideas why?
<sdk:DataGridTemplateColumn Header="Bid Qty" IsReadOnly="True">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{Binding Path=BidPrice.Quantity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource CustomDoubleToStringConverter}}"
Foreground="{Binding Path=BidPrice.TextColour}"
ToolTipService.ToolTip="{Binding Path=BidPrice.ToolTip}"
ToolTipService.Placement="Right" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=BidPrice.Quantity, Mode=TwoWay, Converter={StaticResource CustomDoubleToStringConverter}}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
Code (very simplified) :
// field is bound to BidPrice on this
public class PriceStackLine : INotifyPropertyChanged
{
public ProductPrice BidPrice { get; set; }
}
// BidPrice is a ProductPrice with a Quantity property
public class ProductPrice : INotifyPropertyChanged
{
private double _qty;
public double Quantity
{
get
{
return _qty;
}
set
{
_qty = value;
NotifyPropertyChanged("Quantity");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// converter
public class CustomDoubleToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double num = (double)value;
if (num == 0)
return string.Empty;
return num;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
double result = 0;
double.TryParse(value.ToString(), out result);
return result;
}
}
UPDATE - FIXED! The TextColour was null on the new edits which was probably defaulting to transparent, that is why I couldnt see the edit
I have a list control and each item contains two images and text. On the click on each item I want to hide or show selected image on selected list item.
Here is XAML code snippet:
<ListBox x:Name="list" SelectionChanged="list_SelectionChanged" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<Image Source="{Binding ImagePath}" Stretch="None"/>
<Image Source="{Binding ImagePath}" Stretch="None"
Visibility="{Binding ImageVisibility,
Converter={StaticResource boolVisibilityConverter}}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C# code:
dataSource = new ObservableCollection<ImageData>()
{
new ImageData(){Name = "User1:", ImagePath="/Images/user1.png", ImageVisibility = false},
new ImageData(){Name = "User1:", ImagePath="/Images/user1.png", ImageVisibility = true},
new ImageData(){Name = "User1:", ImagePath="/Images/user1.png", ImageVisibility = true},
new ImageData(){Name = "User2:", ImagePath="/Images/user2.png", ImageVisibility = true}
};
List Selection Changed Event:
private void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
((ImageData)(((object[])(e.AddedItems))[0])).ImageVisibility = false;
list.UpdateLayout();
}
ImageData class:
public class ImageData
{
public string ImagePath { get; set; }
public string Name { get; set; }
public bool ImageVisibility { get; set; }
}
Image Visibility Converter:
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool flag = false;
if (value is bool)
{
flag = (bool)value;
}
else if (value is bool?)
{
bool? nullable = (bool?)value;
flag = nullable.HasValue ? nullable.Value : false;
}
return (flag ? Visibility.Visible : Visibility.Collapsed);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((value is Visibility) && (((Visibility)value) == Visibility.Visible));
}
}
Please help me to accomplish such functionality.
You need to use INotifyPropertyChanged interface http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
For example so:
public class ImageData : INotifyPropertyChanged
{
private bool _imageVisibility;
public string ImagePath { get; set; }
public string Name { get; set; }
public bool ImageVisibility
{
get
{
return _imageVisibility;
}
set
{
_imageVisibility = value;
OnPropertyChanged("ImageVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Full changes according to your task you can see here (dropbox)
I have a WPF-MVVM application...
I have 3 Radio button controls - with three options => "Individual", "Group" and "Both".
All 3 have same Group name...that means only one of these radio button can be selected.
I can have three properties in viewmodel...for each of these three options...and can check which one is selected.
Function()
{
if (Is_Individual_property)
{
// Individual selected
}
if (Is_Group_property)
{
// group selected
}
if (Is_Both_property)
{
// Both selected
}
}
But I think this is not best approach.
Can I have just one property in viewmodel and bind the values accordingly ?
How about having a single property and managing multiple values using a converter. For example:
XAML:
<Grid>
<Grid.Resources>
<local:BooleanToStringValueConverter x:Key="BooleanToStringValueConverter" />
</Grid.Resources>
<StackPanel>
<TextBlock Text="{Binding Property1}" />
<RadioButton Name="RadioButton1"
GroupName="Group1"
Content="Value1"
IsChecked="{Binding Path=Property1, Converter={StaticResource BooleanToStringValueConverter}, ConverterParameter=Value1}" />
<RadioButton Name="RadioButton2"
GroupName="Group1"
Content="Value2"
IsChecked="{Binding Path=Property1, Converter={StaticResource BooleanToStringValueConverter}, ConverterParameter=Value2}" />
<RadioButton Name="RadioButton3"
GroupName="Group1"
Content="Value3"
IsChecked="{Binding Path=Property1, Converter={StaticResource BooleanToStringValueConverter}, ConverterParameter=Value3}" />
</StackPanel>
</Grid>
Converter:
public class BooleanToStringValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (System.Convert.ToString(value).Equals(System.Convert.ToString(parameter)))
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (System.Convert.ToBoolean(value))
{
return parameter;
}
return null;
}
}
Class:
public class MyClass : INotifyPropertyChanged
{
private String _property1;
public String Property1
{
get { return _property1; }
set { _property1 = value; RaisePropertyChanged("Property1"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(String propertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Window:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyClass() { Property1 = "Value1" };
}
}
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.