WiiMaxx
WiiMaxx

Reputation: 5420

how to appropriate a value in xaml to an nonbindable Property

how do i write following code as XAML

Assuming we have this snip of code

        var myValue = someMethodeReturn(); // the return will fit

        // could also be an other nonbindable property
        var myTextBlock = new TextBlock();
        myTextBlock.Inlines = myValue;

how would you convert

       var myValue = someMethodeReturn(); 

and

myTextBlock.Inlines = myValue;

as XAML ONLY

surely the first part could look like ???={Binding myProperty} and the secound part like <TextBlock.Inlines><???/></TextBlock.Inlines> but what would ??? be look like ?

the visual result should be something like this (if you execute youe solution)

    <TextBlock  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightGray" TextWrapping="WrapWithOverflow" >
        <TextBlock.Inlines>
            <Run>meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr  lange run 1</Run>
            <Run Foreground="Green" FontFamily='Palatino Linotype' Typography.Variants='Superscript'>meine run2</Run>
            <Run>meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr  lange run</Run>
            <Run Foreground="LimeGreen" Background="Yellow">meine run3</Run>
            <Run>meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr  lange run</Run>
        </TextBlock.Inlines>
    </TextBlock>

i tested ItemsControl between the TextBlock.Inlines Tages but it returns TextBlocks as default and i wasn't able to set Run or InLine as ItemTemplate

i think i just need a Control that returns a List<Inline> but i doesn't know which Control will be able to do this

any suggestions would be appreciated

i tryed the following thinks

First

<TextBlock  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightGray" TextWrapping="WrapWithOverflow" >
    <TextBlock.Inlines>
        <ContentControl Content="{Binding myBinding}"/>
    </TextBlock.Inlines>
</TextBlock>

returns just "(Auflistung)" as Text

Secound

    <TextBlock  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightGray" TextWrapping="WrapWithOverflow" >
        <TextBlock.Inlines>
            <ItemsControl ItemsSource="{Binding myBinding}"/>
        </TextBlock.Inlines>
    </TextBlock>

returns a List of TextBoxes wrapped in a ContenPresenter

Upvotes: 2

Views: 473

Answers (3)

Stipo
Stipo

Reputation: 4606

Create bindable Inlines attached property as follows:

Bindable.cs

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace WpfApplication
{
    public static class Bindable
    {
        public static readonly DependencyProperty InlinesProperty = DependencyProperty.RegisterAttached("Inlines", typeof(IEnumerable<Inline>), typeof(Bindable), new PropertyMetadata(OnInlinesChanged));

        private static void OnInlinesChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            var textBlock = source as TextBlock;

            if (textBlock != null)
            {
                textBlock.Inlines.Clear();
                var inlines = e.NewValue as IEnumerable<Inline>;
                if (inlines != null)
                    textBlock.Inlines.AddRange(inlines);
            }
        }

        [AttachedPropertyBrowsableForType(typeof(TextBlock))]
        public static IEnumerable<Inline> GetInlines(this TextBlock textBlock)
        {
            return (IEnumerable<Inline>)textBlock.GetValue(InlinesProperty);
        }

        public static void SetInlines(this TextBlock textBlock, IEnumerable<Inline> inlines)
        {
            textBlock.SetValue(InlinesProperty, inlines);
        }
    }
}

And use it like this:

MyViewModel.cs

using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;

namespace WpfApplication
{
    public class MyViewModel
    {
        // This is against MVVM principle - to contain views (Inlines) in view model, but I don't want to complicate by creating ViewModel class for each Inline derived class.
        public IEnumerable<Inline> Inlines { get; private set; }

        public MyViewModel()
        {
            this.Inlines = new Inline[]
            {
                new Run("meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr  lange run 1"),
                new Run("meine run2") { Foreground = Brushes.Green, Typography = { Variants = FontVariants.Superscript } },
                new Run("meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr  lange run"),
                new Run("meine run3") { Foreground = Brushes.LimeGreen, Background = Brushes.Yellow },
                new Run("meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr  lange run")
            };
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication">
    <Window.DataContext>
        <local:MyViewModel/>
    </Window.DataContext>
    <TextBlock local:Bindable.Inlines="{Binding Inlines}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightGray" TextWrapping="WrapWithOverflow"/>
</Window>

Upvotes: 1

Mike Fuchs
Mike Fuchs

Reputation: 12319

My idea would have been to use an ItemsControl and set the ItemPanel to WrapPanel, then insert TextBlocks or ContentPresenters for each item. Turns out those TextBlocks will not wrap as nicely as I'd have expected:

enter image description here

Well, you at least have a ViewModel, so how about putting your text items through the meat grinder before committing them to your source collection? Looks right afterwards:

enter image description here

Now if it would only feel right as well ;)! Maybe you can use some of it, at least.

ViewModel:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private ObservableCollection<MyRunModel> _myRuns = new ObservableCollection<MyRunModel>();
    public ObservableCollection<MyRunModel> MyRuns { get { return _myRuns; } set { _myRuns = value; OnPropertyChanged("MyRuns"); } }


    public ViewModel()
    {
        List<MyRunModel> runs = new List<MyRunModel>();
        runs.Add(new MyRunModel() { Text = "Meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr lange run1" });
        runs.Add(new MyRunModel() { Text = "Meine run2", Foreground = ForegroundDescription.HighlightDark });
        runs.Add(new MyRunModel() { Text = "Meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr lange run3" });
        runs.Add(new MyRunModel() { Text = "Meine run4", Foreground = ForegroundDescription.HighlightLight, Background = BackgroundDescription.Highlight });
        runs.Add(new MyRunModel() { Text = "Meine sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr sehr lange run5" });

        CommitMyRuns(runs);
    }

    /// <summary>
    /// Splits up every run into words (delimited by space), and adds the parts 
    /// to the collection that can be bound to the UI. Retains formatting information.
    /// </summary>
    /// <param name="runs"></param>
    private void CommitMyRuns(List<MyRunModel> runs)
    {
        int runCount = runs.Count;
        for (int i = 0; i < runCount; i++)
        {
            string[] parts = runs[i].Text.Split(' ');
            int partCount = parts.Length;
            for (int j = 0; j < partCount; j++)
            {
                bool isLast = j == parts.Length - 1;
                MyRunModel run = new MyRunModel()
                {
                    Text = parts[j] + (isLast ? string.Empty : " "),  // add space that was lost in split
                    Foreground = runs[i].Foreground, // keep formatting
                    Background = runs[i].Background
                };
                MyRuns.Add(run);
            }
            MyRuns.Add(new MyRunModel() { Text = " " }); // add a space after each of the original runs (unformatted)
        }
    }

}
public class MyRunModel
{
    public string Text { get; set; }
    // do not use UI types (e.g. Brush) directly in viewmodel
    public ForegroundDescription Foreground { get; set; }
    public BackgroundDescription Background { get; set; }
}

public enum ForegroundDescription
{
    None = 0,
    HighlightDark,
    HighlightLight
}

public enum BackgroundDescription
{
    None = 0,
    Highlight
}

Xaml:

<Window.DataContext>
    <local:ViewModel />
</Window.DataContext>

<Window.Resources>
    <SolidColorBrush x:Key="ForegroundHighlightDarkBrush" Color="Green" />
    <SolidColorBrush x:Key="ForegroundHighlightLightBrush" Color="LimeGreen" />
    <SolidColorBrush x:Key="BackgroundHighlightBrush" Color="Yellow" />
</Window.Resources>

<Grid>
    <TextBlock>
        <TextBlock.Inlines>
            <ItemsControl ItemsSource="{Binding MyRuns}" HorizontalContentAlignment="Stretch">
                <ItemsControl.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:MyRunModel}">
                        <TextBlock x:Name="presenter" TextWrapping="Wrap" Text="{Binding Text}"/>
                        <DataTemplate.Triggers>
                            <DataTrigger Binding="{Binding Foreground}" Value="HighlightDark">
                                <Setter TargetName="presenter" Property="TextElement.Foreground" Value="{StaticResource ForegroundHighlightDarkBrush}" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Foreground}" Value="HighlightLight">
                                <Setter TargetName="presenter" Property="TextElement.Foreground" Value="{StaticResource ForegroundHighlightLightBrush}" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Background}" Value="Highlight">
                                <Setter TargetName="presenter" Property="TextElement.Background" Value="{StaticResource BackgroundHighlightBrush}" />
                            </DataTrigger>
                        </DataTemplate.Triggers>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </TextBlock.Inlines>
    </TextBlock>
</Grid>

Subclassing the textbox to make the Inlines bindable might be a possibility. See this answer.

Upvotes: 1

Dreamwalker
Dreamwalker

Reputation: 3035

To add a string value you can use

myTextBlock.Inlines.Add(new Run(myValue));

If myValue is an array loop through and add multiple run elements

Msdn docs for Run class http://msdn.microsoft.com/en-us/library/system.windows.documents.run.aspx

Upvotes: 1

Related Questions