salmelo
4/10/2017 - 7:19 AM

BindTextList.cs

The above files are released under the Creative Commons CC0 license, the full text of which is available here: https://creativecommons.org/publicdomain/zero/1.0/ .
<!--Binding to list of people and showing names. -->
<TextBlock local:BindTextList.Source="{Binding People}"
           local:BindTextList.TextMemberPaths="Name"/>

<!-- Maybe Person has seperate first and last name properties.-->
<TextBlock local:BindTextList.Source="{Binding People}"
           local:BindTextList.TextMemberPaths="FirstName,LastName"
           local:BindTextList.StringFormat="{}{0} {1}"/>

<!--Want each name on a seperate line?-->
<TextBlock local:BindTextList.Source="{Binding People}"
           local:BindTextList.TextMemberPaths="Name"
           local:BindTextList.Seperator="&#10;"/>

<!--Middle initial?-->
<TextBlock local:BindTextList.Source="{Binding People}"
           local:BindTextList.TextMemberPaths="FirstName,LastName,MiddleName[0]"
           local:BindTextList.StringFormat="{}{0} {2}. {1}"/>

<!--Just want to list already formatted strings? -->
<TextBlock local:BindTextList.Source="{Binding Strings}"/>
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;

namespace AppNamespace
{
    public static class BindTextList
    {

        public static IEnumerable<object> GetSource(DependencyObject obj)
        {
            return (IEnumerable<object>)obj.GetValue(SourceProperty);
        }

        public static void SetSource(DependencyObject obj, IEnumerable<object> value)
        {
            obj.SetValue(SourceProperty, value);
        }

        // Using a DependencyProperty as the backing store for Source.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.RegisterAttached("Source", typeof(IEnumerable<object>), typeof(BindTextList), new PropertyMetadata(null, Source_Changed));

        public static string GetTextMemberPaths(DependencyObject obj)
        {
            return (string)obj.GetValue(TextMemberPathsProperty);
        }

        public static void SetTextMemberPaths(DependencyObject obj, string value)
        {
            obj.SetValue(TextMemberPathsProperty, value);
        }

        // Using a DependencyProperty as the backing store for TextMemberPath.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TextMemberPathsProperty =
            DependencyProperty.RegisterAttached("TextMemberPaths", typeof(string), typeof(BindTextList), new PropertyMetadata("", TextMemberPath_Changed));

        public static string GetSeperator(DependencyObject obj)
        {
            return (string)obj.GetValue(SeperatorProperty);
        }

        public static void SetSeperator(DependencyObject obj, string value)
        {
            obj.SetValue(SeperatorProperty, value);
        }

        // Using a DependencyProperty as the backing store for Seperator.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SeperatorProperty =
            DependencyProperty.RegisterAttached("Seperator", typeof(string), typeof(BindTextList), new PropertyMetadata(", "/*, Seperator_Changed*/));

        public static string GetStringFormat(DependencyObject obj)
        {
            return (string)obj.GetValue(StringFormatProperty);
        }

        public static void SetStringFormat(DependencyObject obj, string value)
        {
            obj.SetValue(StringFormatProperty, value);
        }

        // Using a DependencyProperty as the backing store for StringFormat.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StringFormatProperty =
            DependencyProperty.RegisterAttached("StringFormat", typeof(string), typeof(BindTextList), new PropertyMetadata("{0}", StringFormat_Changed));


        private static System.Runtime.CompilerServices.ConditionalWeakTable<TextBlock, ChangeListener> tbTable = new System.Runtime.CompilerServices.ConditionalWeakTable<TextBlock, ChangeListener>();

        private static void Source_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tb = d as TextBlock;
            if (tb == null) return;

            if (e.OldValue != e.NewValue)
            {
                if (e.OldValue is INotifyCollectionChanged oldNCC)
                {
                    if (tbTable.TryGetValue(tb, out var oldListener))
                    {
                        CollectionChangedEventManager.RemoveListener(oldNCC, oldListener);
                        tbTable.Remove(tb);
                    }
                }

                if (e.NewValue is INotifyCollectionChanged newNCC)
                {
                    var listener = new ChangeListener(tb);
                    tbTable.Add(tb, listener);
                    CollectionChangedEventManager.AddListener(newNCC, listener);
                }
            }

            CreateInlines(tb);
        }

        //private static void Seperator_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        //{
        //    var tb = d as TextBlock;
        //    if (tb != null) CreateInlines(tb);
        //}

        private static void TextMemberPath_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tb = d as TextBlock;
            if (tb != null) CreateInlines(tb);
        }

        private static void StringFormat_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tb = d as TextBlock;
            if (tb != null) CreateInlines(tb);
        }

        private static void CreateInlines(TextBlock tb)
        {
            var source = GetSource(tb);

            tb.Inlines.Clear();

            if (source == null) return;

            var sep = GetSeperator(tb);
            var textPath = GetTextMemberPaths(tb);
            var format = GetStringFormat(tb);

            var paths = Regex.Split(textPath, @"(?<!\^),");

            var enumerator = source.GetEnumerator();

            if (!enumerator.MoveNext()) return;

            while (true)
            {
                BindingBase b;
                if (paths.Length == 1)
                {
                    b = new Binding(textPath)
                    {
                        Source = enumerator.Current,
                        StringFormat = format,
                        Mode = BindingMode.OneWay
                    };
                }
                else
                {
                    var mb = new MultiBinding()
                    {
                        StringFormat = format,
                        Mode = BindingMode.OneWay
                    };

                    foreach (var path in paths)
                    {
                        mb.Bindings.Add(new Binding(path) { Source = enumerator.Current });
                    }

                    b = mb;
                }

                var r = new Run();
                r.SetBinding(Run.TextProperty, b);

                tb.Inlines.Add(r);

                if (!enumerator.MoveNext()) break;

                b = new Binding()
                {
                    Path = new PropertyPath(SeperatorProperty),
                    Source = tb
                };

                r = new Run();
                r.SetBinding(Run.TextProperty, b);

                tb.Inlines.Add(r);
            }
        }

        private class ChangeListener : IWeakEventListener
        {
            public TextBlock TextBlock { get; }

            public ChangeListener(TextBlock tb)
            {
                TextBlock = tb;
            }

            public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
            {
                if (managerType != typeof(CollectionChangedEventManager)) return false;

                CreateInlines(TextBlock);
                return true;
            }
        }
    }
}