Right. I've got a small program (that replicates my issue). Basically, it tries to bind to some properties of the object it's styling. It kind of works: it gives me the default value (from the dependency property). I've been thinking this may be because the Style's RelativeSource Self isn't the same as the TextBox it's styling's one. But I don't know. I've tried debugging this, checking time and again that the value set in XAML was actually set. The thing is, with a smaller test program it works. This is just a scale up from that. I don't know what's going wrong.
Thanks!
The code for reproducing this issue:
MainWindow.xaml
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
Title="My title." Height="350" Width="425" MaxHeight="350" MaxWidth="425" MinHeight="350" MinWidth="425">
<Window.Resources>
<ResourceDictionary Source="TestDictionary.xaml"/>
</Window.Resources>
<Grid>
<TextBox Style="{StaticResource TextBoxWithDefault}" FontSize="36" lcl:MyOptions.Default="Not default." VerticalAlignment="Center"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public static class MyOptions
{
public static string GetDefault(DependencyObject obj)
{
return (string)obj.GetValue(DefaultProperty);
}
public static void SetDefault(DependencyObject obj, string value)
{
obj.SetValue(DefaultProperty, value);
}
public static readonly DependencyProperty DefaultProperty =
DependencyProperty.RegisterAttached(
"Default",
typeof(string),
typeof(MyOptions),
new PropertyMetadata("Default"));
}
}
TestDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style TargetType="TextBox" x:Key="TextBoxWithDefault">
<Style.Resources>
<Label Content="{Binding Path=(lcl:MyOptions.Default), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
Foreground="LightGray"
FontSize="{Binding Path=(FontSize), Mode=TwoWay, RelativeSource={RelativeSource Self}}" x:Key="TheLabel"/>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None" Visual="{DynamicResource TheLabel}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None" Visual="{DynamicResource TheLabel}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
I have no idea what's going wrong here, as a scaled down version of this works perfectly. There's probably something I overlooked, that will seem pretty obvious when I find it. But I can't find it now.
EDIT: Well, it seems I was dumb. The original version (here) uses a Trigger, which means that it gets the parent textbox's value. The question now is: how can I get it working?
Thanks for your time!
The real show-stopper here is that when you use the Label in a VisualBrush, the label isn't part of the TextBox' "Visual Tree" (see for example Sheldon Xiao's answer to this similar question on MSDN: Binding Problem inside VisualBrush).
This means that the label won't inherit the text box' DataContext, and you can't reach the text box from a RelativeSource binding either. In contrast, the accepted answer in your other post sets the actual content of a button, which does make the content part of the button's visual tree.
So I don't think there's a pure XAML solution to this problem - pushing the correct MyOptions.Default from the text box to the label. One possible code-based solution is to scrap the TextBoxWithDefault style and do everything from your attached property when Default changes:
...
public static readonly DependencyProperty DefaultProperty =
DependencyProperty.RegisterAttached(
"Default",
typeof(string),
typeof(MyOptions),
//Listen for changes in "Default":
new PropertyMetadata(null, OnMyDefaultChanged));
private static void OnMyDefaultChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var text = (TextBox)sender;
var myDefault = e.NewValue;
var defaultLabel = new Label();
defaultLabel.Foreground = Brushes.LightGray;
//Explicitly bind the needed value from the TextBox:
defaultLabel.SetBinding(Label.ContentProperty,
new Binding()
{
Source = text,
Path = new PropertyPath(MyOptions.DefaultProperty)
});
text.Background = new VisualBrush()
{
Visual = defaultLabel,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Center,
Stretch = Stretch.None
};
text.TextChanged += new TextChangedEventHandler(OnTextWithDefaultChanged);
}
private static void OnTextWithDefaultChanged(object sender,
TextChangedEventArgs e)
{
var text = (TextBox)sender;
var defaultLabel = (text.Background as VisualBrush).Visual as Label;
defaultLabel.Visibility = string.IsNullOrEmpty(text.Text) ?
Visibility.Visible :
Visibility.Collapsed;
}
Related
I'm trying to further customize build-in capability of WPF ListBox for showing items in groups.
In short, I want to hide Group's container (and Group's title altogether) if all items inside group are collapsed (Visibility property).
First, I have very simple class City that represent single Item. This class include Shown property. Inside ItemContainerStyle I simply have DataTrigger that set Visibility to Collapsed if value of this property is False.
class City : INotifyPropertyChanged
{
private bool m_Shown = true;
public string Name { get; set; }
public string Country { get; set; }
public bool Shown
{
get
{
return m_Shown;
}
set
{
m_Shown = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Shown"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This is how I add sample cities, add Group description and all work fine.
m_cities = new List<City>
{
new City() { Name = "Berlin", Country = "Germany" },
new City() { Name = "Milano", Country = "Italy" },
new City() { Name = "Frankfurt", Country = "Germany" },
new City() { Name = "Rome", Country = "Italy" }
};
ICollectionView view = CollectionViewSource.GetDefaultView(m_cities);
view.GroupDescriptions.Add(new PropertyGroupDescription("Country"));
Cities = view; // <-- Binds to ItemsSource of ListBox
I tried in several ways to automatically hide Group if there are no more items visible in it (all are collapsed), but all without luck.
One way is to repeat last 3 lines in code above and this works, but I noticed slowdown with this method and listbox must work fast for user.
Bellow is one of my examples and this actually worked for hiding, but I can't bring group to be visible anymore after that. I tried with converters and similar, but I can't get group visible again.
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Style.Triggers>
<Trigger Property="ActualHeight" Value="20">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<TextBlock Text="{Binding Path=Name}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
Thanks for any help.
A bit (!) late with this, but hopefully it might help someone else in the future.
Inside the control template of most (all?) GroupItem styles is an ItemsPresenter that is used to host and display the child items that belong to the group. It stands to reason that, if all of the child items are collapsed, this ItemsPresenter will have a height of zero.
Therefore, you can add a trigger to the control template based on this condition, and set the Visibility of the whole group item accordingly. A normal property trigger doesn't seem to work, but a data trigger will. Something like this:
<ControlTemplate>
<StackPanel x:Name="Root">
...
<ItemsPresenter x:Name="ItemsPresenter" />
...
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ActualHeight, ElementName=ItemsPresenter}" Value="0">
<Setter TargetName="Root" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
You will need to name the root element of the control template (in this example it's a StackPanel element I've named "Root") and also the ItemsPresenter element (I've just called it "ItemsPresenter"). Obviously the root element might be a different type, and you can use whichever names you like.
You were on the right track, but you needed to bind to the ActualHeight of the ItemsPresenter, and it needed to be a data trigger not a normal property trigger.
I have this code in my xaml which says to color my button when I hover my mouse and click my mouse over the button.
<Border x:Class="DatasetGrid.RowHeaderButton"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" MinWidth="30" Width="Auto">
<Border.Resources>
<SolidColorBrush x:Key="ButtOverBrush" Color="#53C3D5" Opacity="0.2"></SolidColorBrush>
<SolidColorBrush x:Key="ButtPressedBrush" Color="#53C3D5" Opacity="0.5"></SolidColorBrush>
</Border.Resources>
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource ButtOverBrush}"></Setter>
</Trigger>
<DataTrigger Binding="{Binding IsMouseDown, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="{StaticResource ButtPressedBrush}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
This works all well and good, but I find that as soon as I change the Background color in code behind, the above MouseOver and MouseDown triggers don't fire anymore.
RowHeaderButton rhb = RowHeadersColumn.VisibleRowHeaders[cell.CellInfo.RowIndex];
rhb.Background = new SolidColorBrush(Color.FromArgb(100, 83, 195, 213));
I'm quite new to WPF so I'm not sure what's going wrong.
Edit:
So to give some more information, my control above is a RowHeaderButton, i.e the row header to a grid. Each row in the grid has it's own row header button. So when the user hovers over or clicks it, it should change from white to the specified SolidColorBrush above.
In the code behind of another control, DataGrid.xaml.cs, I have the below code (simplified) which will change the color of the row header when when a cell in the same row of the grid is selected or not.
void UpdateSelectedCells() {
foreach (Cell cell in VisibleColumns.SelectMany(c => c.VisibleCells))
{
int cellRowIndex = cell.CellInfo.RowIndex;
cell.IsSelected = SelectedCells.Contains(cell.CellInfo);
foreach (RowHeaderButton rhb in RowHeadersColumn.VisibleRowHeaders)
{
int rowHeaderIndex = Convert.ToInt16(rhb._default.Text) - 1;
if (cellRowIndex == rowHeaderIndex)
{
if (cell.IsSelected)
{
rhb.Background = new SolidColorBrush(Color.FromArgb(100, 83, 195, 213));
}
else
{
bool rowselected = false;
//need to check if any other cell in the row is selected, if not then color row header white
foreach (CellInfo celll in SelectedCells)
{
if (celll.RowIndex == cellRowIndex)
{
rowselected = true;
break;
}
}
if (rowselected == false)
rhb.Background = Brushes.White;
}
}
}
}
}
I don't have a ViewModel for this.
The triggers are firing, but their setters are being overridden.
This is due to Dependency Property Value Precendence. If the Background property is set programmatically or as an attribute in the XAML, that value will override anything value any style setter gives it. In general, this is desirable behavior: You want to be able to override what the style does on an individual control.
The solution to this is to do all of your background brush changes in style triggers. Your code behind must have some reason for setting the background brush when it does. Whatever that is, find a way to do it with a trigger. Set a property on the viewmodel and write a trigger on that property.
If you need help translating that high level abstraction into your own code, please share enough code for me to understand why and where the codebehind is setting the Background, and what (if anything) you have for a viewmodel.
I solved the issue by creating a new Dependancy Property and binding it to a data trigger.
public bool IsCellSelected
{
get { return (bool)GetValue(IsCellSelectedProperty); }
set { SetValue(IsCellSelectedProperty, value); }
}
public static readonly DependencyProperty IsCellSelectedProperty =
DependencyProperty.Register("IsCellSelected", typeof(bool), typeof(RowHeaderButton), new PropertyMetadata(null));
In my xaml I have:
<DataTrigger Binding="{Binding IsCellSelected, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="{StaticResource ButtPressedBrush}"></Setter>
</DataTrigger>
And in my code behind I set the value using:
RowHeaderButton rhb = RowHeadersColumn.VisibleRowHeaders[cell.CellInfo.RowIndex];
rhb.IsCellSelected = true; //or false
Now my button hover and button click events are not overridden.
I've been set to maintain a wpf application where there is a listbox for logging purposes.
The items displayed using listbox are of type TextMessage, i.e. the listbox is bound to these text messages via
ObservableCollection<TextMessage> Messages;
listBox.DataContext = Messages;
Messages are then added with something like
Messages.Add(new TextMessage("Test", TypeOfMessage.Headline));
This is the definition of the class TextMessage
public enum TypeOfMessage
{
Normal,
Headline,
Focus,
Important,
Fail,
Success
}
public class TextMessage
{
public TextMessage(string content, TypeOfMessage typeOfMessage)
{
Content = content;
TypeOfMessage = typeOfMessage;
CreationTime = DateTime.Now;
}
public string Content { get; }
public TypeOfMessage TypeOfMessage { get; }
public DateTime CreationTime { get; }
}
The xaml definition for the listbox is something like this:
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="196" Margin="101,77,0,0" VerticalAlignment="Top" Width="256" ItemsSource="{Binding}" SelectionMode="Multiple">
<ListBox.InputBindings>
<KeyBinding
Key="C"
Modifiers="Control"
Command="Copy"
/>
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding
Command="Copy"
Executed="DoPerformCopy"
/>
</ListBox.CommandBindings>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="TextToShow" Text="{Binding Content}"></TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding TypeOfMessage}" Value="Normal">
<Setter TargetName="TextToShow" Property="Foreground" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding TypeOfMessage}" Value="Focus">
<Setter TargetName="TextToShow" Property="Foreground" Value="Black"/>
<Setter TargetName="TextToShow" Property="FontWeight" Value="Bold"/>
</DataTrigger>
<DataTrigger Binding="{Binding TypeOfMessage}" Value="Headline">
<Setter TargetName="TextToShow" Property="Foreground" Value="RoyalBlue"/>
<Setter TargetName="TextToShow" Property="FontWeight" Value="Bold"/>
</DataTrigger>
<DataTrigger Binding="{Binding TypeOfMessage}" Value="Important">
<Setter TargetName="TextToShow" Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding TypeOfMessage}" Value="Fail">
<Setter TargetName="TextToShow" Property="Foreground" Value="Red"/>
<Setter TargetName="TextToShow" Property="FontWeight" Value="Bold"/>
</DataTrigger>
<DataTrigger Binding="{Binding TypeOfMessage}" Value="Success">
<Setter TargetName="TextToShow" Property="Foreground" Value="Green"/>
<Setter TargetName="TextToShow" Property="FontWeight" Value="Bold"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This works nicely (i.e messages are displayed in the listbox in different font weight and color depending on their type), but now for the question :
Is there any way using BindingExpression or any other means to get the font formatting and coloring from code behind from the xaml definitions ?
The reason is that I want to just have the formatting in one place (just in the xaml as it is right now) but still be able to reuse it when I want to copy the contents (using code behind) including font formatting to the clipboard.
Example:
private void DoPerformCopy()
{
RichTextBox rtb = new RichTextBox();
foreach (TextMessage message in (listBox as ListBox)?.SelectedItems.Cast<TextMessage>().ToList())
{
TextPointer startPos = rtb.CaretPosition;
rtb.AppendText(message.Content);
rtb.Selection.Select(startPos, rtb.CaretPosition.DocumentEnd);
//
// Here it would be very nice to instead having multiple switch statements to get the formatting for the
// TypeOfMessage from the xaml file.
SolidColorBrush scb = new SolidColorBrush(message.TypeOfMessage == TypeOfMessage.Fail ? Colors.Red);
//
rtb.Selection.ApplyPropertyValue(RichTextBox.ForegroundProperty, scb);
}
// Now copy the whole thing to the Clipboard
rtb.Selection.Select(rtb.Document.ContentStart, rtb.Document.ContentEnd);
rtb.Copy();
}
Since I'm new to wpf, I'd really appreciate if someone has a tip for solving this. (I've tried hard to find an solution here at stackoverflow, but so far I've been unsuccessful)
Thanks in advance,
King regards
Magnus
Make a ContentPresenter with Content set to your TextMessage. Set the ContentTemplate to listBox.ItemTemplate and apply the template. It will create the visuals (TextBlock in this case). Then, just parse off the values from the TextBlock.
Also, your RichTextBox selection code wasn't working quite right so I fixed that by just inserting TextRanges to the end of it instead of trying to get the selection right.
private void DoPerformCopy(object sender, EventArgs e)
{
RichTextBox rtb = new RichTextBox();
foreach (TextMessage message in (listBox as ListBox)?.SelectedItems.Cast<TextMessage>().ToList())
{
ContentPresenter cp = new ContentPresenter();
cp.Content = message;
cp.ContentTemplate = listBox.ItemTemplate;
cp.ApplyTemplate();
var tb = VisualTreeHelper.GetChild(cp, 0) as TextBlock;
var fg = tb.Foreground;
var fw = tb.FontWeight;
var tr = new TextRange(rtb.Document.ContentEnd, rtb.Document.ContentEnd);
tr.Text = message.Content;
tr.ApplyPropertyValue(RichTextBox.ForegroundProperty, fg);
tr.ApplyPropertyValue(RichTextBox.FontWeightProperty, fw);
}
// Now copy the whole thing to the Clipboard
rtb.Selection.Select(rtb.Document.ContentStart, rtb.Document.ContentEnd);
rtb.Copy();
}
I have created a UserControl called ImageButton, and I am using a DropShadowEffect on MouseOver to show the button as 'active'. However, I cannot seem to bind the Color property of my DropShadowEffect. Could anyone suggest why this doesn't work?
XAML;
<ControlTemplate x:Key="ActiveEffectTemplate" TargetType="{x:Type Controls:ImageButton}">
<Image Name="image" Source="{TemplateBinding ImageSource}">
<Image.Effect>
<DropShadowEffect
Color="{Binding HighlightColour}"
BlurRadius="20"
ShadowDepth="0"
Opacity="1"
Direction="0"/>
</Image.Effect>
</Image>
</ControlTemplate>
Code behind;
public static readonly DependencyProperty HighlightColourProperty =
DependencyProperty.Register("HighlightColour", typeof(Color), typeof(ImageButton));
public Color HighlightColour
{
get { return (Color)GetValue(HighlightColourProperty); }
set { SetValue(HighlightColourProperty, value); }
}
I believe I solved this problem by putting the following into my binding;
RelativeSource={RelativeSource AncestorType={x:Type Controls:ImageButton}}
That binding is relative to the DataContext, it should probably just be a TemplateBinding as well.
I have one TextBlock having width say 100. When the text length is a large one I want to show the characters that is accomodated in that textblock and a (...) button besides the text to specify user that more text is also there. Upon click on that (...) button, the full text will be shown in a separate pop up window.
So i want how the dynamic (...) button will be shown whenever the text length exceed the size of the textblock. Please answer
This isn't exactly what you want, but it's a similar idea and just uses the baked-in stuff:
<TextBlock MaxWidth="200"
Text="{Binding YourLongText}"
TextTrimming="WordEllipsis"
ToolTip="{Binding YourLongText}" />
So you have a TextBlock with a maximum width, and when the text can't fit it displays an ellipsis ("..."). Hovering over the TextBlock with your mouse will show the full text in a ToolTip.
Just experience the same requirement for adding ellipsis on button so adding the solution here
<Style x:Key="editButton" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center" >
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"></Setter>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
Notice the resources in content presenter.
I believe what you want is to set the TextTrimming property. Settng it to WordElilipsis or CharacterEllipsis should provide what you need.
My solution to the problem is probably overkill, but allows for some configuration and control.
I created a behavior that allows me to set the character limit for each binding.
internal class EllipsisStringBehavior
{
public static readonly DependencyProperty CharacterLimitDependencyProperty = DependencyProperty.RegisterAttached("CharacterLimit", typeof(int), typeof(EllipsisStringBehavior), new PropertyMetadata(255, null, OnCoerceCharacterLimit));
public static readonly DependencyProperty InputTextDependencyProperty = DependencyProperty.RegisterAttached("InputText", typeof(string), typeof(EllipsisStringBehavior), new PropertyMetadata(string.Empty, OnInputTextChanged));
// Input Text
public static string GetInputText(DependencyObject dependencyObject)
{
return Convert.ToString(dependencyObject.GetValue(InputTextDependencyProperty));
}
public static void SetInputText(DependencyObject dependencyObject, string inputText)
{
dependencyObject.SetValue(InputTextDependencyProperty, inputText);
}
// Character Limit
public static int GetCharacterLimit(DependencyObject dependencyObject)
{
return Convert.ToInt32(dependencyObject.GetValue(CharacterLimitDependencyProperty));
}
public static void SetCharacterLimit(DependencyObject dependencyObject, object characterLimit)
{
dependencyObject.SetValue(CharacterLimitDependencyProperty, characterLimit);
}
private static void OnInputTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textblock = (TextBlock)d;
string input = e.NewValue == null ? string.Empty : e.NewValue.ToString();
int limit = GetCharacterLimit(d);
string result = input;
if (input.Length > limit && input.Length != 0)
{
result = $"{input.Substring(0, limit)}...";
}
textblock.Text = result;
}
private static object OnCoerceCharacterLimit(DependencyObject d, object baseValue)
{
return baseValue;
}
}
I then simply add the using to my user control...
<UserControl
xmlns:behavior="clr-namespace:My_APP.Helper.Behavior"
d:DesignHeight="300" d:DesignWidth="300">
...and apply the behavior to the TextBlock control I wish to use it on.
<TextBlock Margin="0,8,0,8"
behavior:EllipsisStringBehavior.CharacterLimit="10"
behavior:EllipsisStringBehavior.InputText="{Binding Path=DataContext.FeedItemTwo.Body, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignSubheadingTextBlock}"
FontSize="14"/>
Hope this helps.