ViewBox makes RichTextBox lose its caret - wpf

RichTextBox is placed inside a ViewBox and zoomed to various levels 10 - 1000%. At percentages less than 100%, caret disappears at random cursor locations.
I understand that when a visual is zoomed out (compressed), it will loose pixels. Is there any way that I can stop loosing my cursor?
<Viewbox>
<RichTextBox Name="richTextBox1" Width="400" Height="400" />
</Viewbox>

FINAL EDIT:
hey there, just wanted to say, you can even get this working without reflection at all!! This is not optimized code, I'll leave that for yourself. Also this is still relying on internal stuff. So here it comes:
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
rtb.LayoutUpdated += (sender, args) =>
{
var child = VisualTreeHelper.GetChild(vb, 0) as ContainerVisual;
var scale = child.Transform as ScaleTransform;
rtb.ScaleX = scale.ScaleX;
};
}
}
public class RTBwithVisibleCaret:RichTextBox
{
private UIElement _flowDocumentView;
private AdornerLayer _adornerLayer;
private UIElement _caretSubElement;
private ScaleTransform _scaleTransform;
public RTBwithVisibleCaret()
{
LayoutUpdated += (sender, args) =>
{
if (!IsKeyboardFocused) return;
if(_adornerLayer == null)
_adornerLayer = AdornerLayer.GetAdornerLayer(_flowDocumentView);
if (_adornerLayer == null || _flowDocumentView == null) return;
if(_scaleTransform != null && _caretSubElement!= null)
{
_scaleTransform.ScaleX = 1/ScaleX;
_adornerLayer.Update(_flowDocumentView);
}
else
{
var adorners = _adornerLayer.GetAdorners(_flowDocumentView);
if(adorners == null || adorners.Length<1) return;
var caret = adorners[0];
_caretSubElement = (UIElement) VisualTreeHelper.GetChild(caret, 0);
if(!(_caretSubElement.RenderTransform is ScaleTransform))
{
_scaleTransform = new ScaleTransform(1 / ScaleX, 1);
_caretSubElement.RenderTransform = _scaleTransform;
}
}
};
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var cthost = GetTemplateChild("PART_ContentHost") as FrameworkElement;
_flowDocumentView = cthost is ScrollViewer ? (UIElement)((ScrollViewer)cthost).Content : ((Decorator)cthost).Child;
}
public double ScaleX
{
get { return (double)GetValue(ScaleXProperty); }
set { SetValue(ScaleXProperty, value); }
}
public static readonly DependencyProperty ScaleXProperty =
DependencyProperty.Register("ScaleX", typeof(double), typeof(RTBwithVisibleCaret), new UIPropertyMetadata(1.0));
}
working with this XAML:
<Window x:Class="RTBinViewBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:RTBinViewBoxTest="clr-namespace:RTBinViewBoxTest" Title="MainWindow" Height="350" Width="525">
<Viewbox Height="100" x:Name="vb">
<RTBinViewBoxTest:RTBwithVisibleCaret Width="70" x:Name="rtb">
<FlowDocument>
<Paragraph>
<Run>long long long long long long long long long long long long long long long long long long long long long long long long text</Run>
</Paragraph>
</FlowDocument>
</RTBinViewBoxTest:RTBwithVisibleCaret>
</Viewbox>
</Window>
yeah, it got me thinking when I saw, that all these are accessible through the visual tree! Instead of inheriting from RichTextBox (which was needed to get the TemplateChild) you can also traverse the VisualTree to get to that FlowDocumentView!
original post:
ok, let's look at what your options are:
as stated in my comment above: the easiest way to accomplish this whould be to have RichTextBox's content zoom instead of the RichTextBox being inside a ViewBox. You haven't answered (yet) if this would be an option.
now everything else will get complex and is more or less problematic:
you can use Moq or something similar (think Moles or so...) to replace the getter of SystemParameters.CaretWidth to accommodate for the ScaleTransform the ViewBox exerts. This has several problems! First: these Libraries are designed for use in testing scenarios and not recommended for production use. Second: you would have to set the value before the RichTextBox instantiates the Caret. That'd be tough though, as you don't know beforehand how the ViewBox scales the RichTextBox. So, this is not a good option!
the second (bad) option would be to use Reflection to get to this nice little Class System.Windows.Documents.CaretElement. You can get there through RichTextBox.TextEditor.Selection.CaretElement (you have to use Reflection as these Properties and Classes are for the most part internal sealed). As this is an Adorner you might be able to attach a ScaleTransform there that reverses the Scaling. I have to say though: this is neither tested nor recommended!
Your options are limited here and if I were you I'd go for my first guess!
EDIT:
If you really want to get down that second (bad) route you might have more luck if you apply that ScaleTransform to that adorners single child that you can get through the private field _caretElement of type CaretSubElement. If I read that code right, then that subelement is your actual Caret Visual. The main element seems to be used for drawing selection geometry. If you really want to do this, then apply that ScaleTransform there.
EDIT:
complete example to follow:
XAML:
<Window x:Class="RTBinViewBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Viewbox Height="100" x:Name="vb">
<RichTextBox Width="70" Name="rtb">
<FlowDocument>
<Paragraph>
<Run>long long long long long long long long long long long long long long long long long long long long long long long long text</Run>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Viewbox>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
rtb.GotFocus +=RtbOnGotFocus;
}
private void RtbOnGotFocus(object s, RoutedEventArgs routedEventArgs)
{
rtb.LayoutUpdated += (sender, args) =>
{
var child = VisualTreeHelper.GetChild(vb, 0) as ContainerVisual;
var scale = child.Transform as ScaleTransform;
rtb.Selection.GetType().GetMethod("System.Windows.Documents.ITextSelection.UpdateCaretAndHighlight", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(
rtb.Selection, null);
var caretElement=rtb.Selection.GetType().GetProperty("CaretElement", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rtb.Selection, null);
if (caretElement == null)
return;
var caretSubElement = caretElement.GetType().GetField("_caretElement", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(caretElement) as UIElement;
if (caretSubElement == null) return;
var scaleTransform = new ScaleTransform(1/scale.ScaleX, 1);
caretSubElement.RenderTransform = scaleTransform;
};
}
}
this works for me. everything said.

AFAIK you can't really solve this. ViewBox is using ScaleTransform under the covers, and ScaleTransform, when scaling down, will hide certain lines, since it cannot display everything. the ScaleTransform is not using a very advanced algorithm to do the scale,(it just does it in the fastest way possible) and I don't think you can change that..

Related

Dynamically modifying a Path, Geometry, Shape in XAML

I am stuck. I want to do some sophisticated animations in XAML, in which the geometry of the image gets modified at runtime. I want to start out simple, and then make something far more interesting, but nothing seems to work.
For now, all I want to do is draw an arc on the screen using a for-loop, and this arc will be modified by a slider. The slider will control the starting point of the arc. So, if I have the slider at Pi/4, it will draw an arc from Pi/4 to 2Pi.
I've tried so many different ways. Right now I've created a class of the type Shape, and tried to modify the DefiningGeometry which is a property of the Shape class. The startRadians gets modified by the slider, so that part works OK, I got the binding to work. But, after startRadians gets changed (it is a DependencyProperty, btw) I want the class to re-calculate the geometry of the circle. (Like a cherry pie that is missing a bigger piece as startRadians gets changed.) The real problem is that DefiningGeometry is a read-only property, so I can't change it on the fly. (Am I right about this?) Even if I could, I don't know the way to write the line of code so that DrawMyArc fires again, and the results get reloaded into DefiningGeometry.
OK, so I need some guidance. Should I change the parent class, so that I have an easily modifiable Path/geometry? I am at a loss here.
Should I use an entirely different approach, like where you dynamically make/delete the geometry using StreamGeometry?
Here's the relevant code:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace January14noon
{
public class myCircle : Shape
{
public double startRadians
{
get { return (double)GetValue(startRadiansProperty); }
set { SetValue(startRadiansProperty, value); }
}
protected override Geometry DefiningGeometry
{
get
{
return DrawMyArc(100, 200, true, 40, 40, 360, startRadians, 2 * Math.PI);
}
}
// Using a DependencyProperty as the backing store for startRadians. This enables animation, styling, binding, etc...
public static readonly DependencyProperty startRadiansProperty =
DependencyProperty.Register("startRadians", typeof(double), typeof(myCircle), new PropertyMetadata(Math.PI / 4, new PropertyChangedCallback(startRadians_PropertyChanged)));
private static void startRadians_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//
//
}
public PathGeometry DrawMyArc(double centX, double centY, bool CW, double radiusX, double radiusY, int numberOfSegs, double startRad, double endRad)
{
double[,] rawPoints = new double[numberOfSegs, 2];
List<LineSegment> segments = new List<LineSegment>();
double arcLength;
arcLength = endRad - startRad;
for (int i = 0; i < numberOfSegs; i++)
{
rawPoints[i, 0] = radiusX * Math.Sin(i * (arcLength / numberOfSegs) + startRad) + centX;
rawPoints[i, 1] = radiusY * -Math.Cos(i * (arcLength / numberOfSegs) + startRad) + centY;
segments.Add(new LineSegment(new Point(rawPoints[i, 0], rawPoints[i, 1]), true));
}
LineSegment[] segArray = segments.ToArray();
PathFigure figure = new PathFigure(new Point(centX, centY), segments, false);
PathGeometry myGeometry = new PathGeometry();
myGeometry.Figures.Add(figure);
return myGeometry;
}
}
}
And XAML:
<Window x:Class="January14noon.MainWindow"
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:January14noon"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:myCircle x:Key="myCircleDataSource" d:IsDataSource="True"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource myCircleDataSource}}">
<Slider x:Name="slider" VerticalAlignment="Top" Margin="0,0,163.898,0" Value="{Binding startRadians, Mode=OneWayToSource}"/>
<TextBox x:Name="myTextBox" HorizontalAlignment="Right" Height="23" TextWrapping="Wrap" Text="{Binding startRadians}" VerticalAlignment="Top" Width="147.797"/>
<!--<local:myCircle x:Name="instanceOfCircle" />-->
<local:myCircle Stroke="Black" StrokeThickness="2"/>
</Grid>
</Window>
Any help would be appreciated. Just general approach, something specific even words of encouragement.
TYIA
Any dependency property that affects visual appearance of a control might be registered with appropriate FrameworkPropertyMetadataOptions, e.g. AffectsMeasure, AffectsArrange or AffectsRender.
Note also that class, property and method names in C# are supposed to use Pascal Casing, i.e. start with an uppercase letter
public static readonly DependencyProperty StartRadiansProperty =
DependencyProperty.Register(nameof(StartRadians), typeof(double), typeof(MyCircle),
new FrameworkPropertyMetadata(Math.PI / 4,
FrameworkPropertyMetadataOptions.AffectsMeasure, StartRadiansPropertyChanged));
public double StartRadians
{
get { return (double)GetValue(StartRadiansProperty); }
set { SetValue(StartRadiansProperty, value); }
}
private static void StartRadiansPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
...
}
Make sure you bind the visible circle to the data source:
<Window.Resources>
<local:myCircle x:Key="myCircleDataSource" d:IsDataSource="True"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource myCircleDataSource}}">
<Slider x:Name="slider" VerticalAlignment="Top" Margin="0,0,163.898,0" Value="{Binding startRadians, Mode=OneWayToSource}"/>
<TextBox x:Name="myTextBox" HorizontalAlignment="Right" Height="23" TextWrapping="Wrap" Text="{Binding startRadians}" VerticalAlignment="Top" Width="147.797"/>
<!--<local:myCircle x:Name="instanceOfCircle" />-->
<local:myCircle Stroke="Black" StrokeThickness="2" startRadians="{Binding startRadians}"/>
</Grid>
and invalidate the visual when the property changes:
private static void startRadians_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var circle = (myCircle)d;
circle.InvalidateVisual(); // <-- Invalidate!
}
Invalidating will tell engine to re-render this visual, which will call your DrawMyArc() method

Dynamic Heights and Scrolling based on available space in a stackpanel

This is hard to explain:
I have two lists that, each can contain a large number of items, and they are placed one on top of the other so like this:
<StackPanel>
<List Name="ListOne"/>
<List Name="ListTwo"/>
</StackPanel>
ListOne has a little bit of a margin at the bottom to make it look neat.
The stack panel is contained within another control that has a fixed height. Say there is enough room for 8 items between the two lists before it overflows.
What I want is that when there is 8 items between the two lists there is no scroll bars, even if there is an uneven spread like 7-1 , 4-4, 3-5 etc. and once there is more than 8 elements then a scroll bar appears but only where it's needed.
For example if ListOne has 5 elements and ListTwo has 4 elements then ListOne has a scroll bar and they are both the same height, where as if it's 6-3 then ListOne has a scroll bar and takes up as much room as it can while still giving ListTwo enough room to display without needing a scrollbar.
Any idea how that would be possible? Not sure if that makes much sense but I'm having difficulty explaining it. Will reply quickly if you leave a comment. Thanks in advance.
OK so I have something that appears to work to a decent level using an attached property.
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace Sus.Common.UI
{
public class PanelHeightManagerExtension
{
public static readonly DependencyProperty ManageHeightsProperty =
DependencyProperty.RegisterAttached("ManageHeights",
typeof(bool),
typeof(PanelHeightManagerExtension),
new UIPropertyMetadata(false, OnManageHeightsChanged));
public static bool GetManageHeights(DependencyObject obj)
{
return (bool)obj.GetValue(ManageHeightsProperty);
}
public static void SetManageHeights(DependencyObject obj, bool value)
{
obj.SetValue(ManageHeightsProperty, value);
}
private static void OnManageHeightsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = ((Panel)d);
element.Loaded += (sender, args) => Update((Panel)sender);
}
private static void Update(Panel panel)
{
var children = panel.Children.OfType<FrameworkElement>().OrderBy(x=>x.ActualHeight).ToList();
var remainingHeight = panel.DesiredSize.Height;
for (int i = 0; i < children.Count; i++)
{
if (children[i].ActualHeight > remainingHeight / (children.Count - i))
{
remainingHeight -= children[i].MaxHeight = remainingHeight / (children.Count - i);
}
else
{
remainingHeight -= children[i].ActualHeight;
children[i].MaxHeight = children[i].ActualHeight;
}
}
}
}
}
And you just add the attached property as follows:
<Grid ui:PanelHeightManagerExtension.ManageHeights="True">
Still open to better answers
StackPanel will give its children all the space they want, so as you can see ListOne can push ListTwo out of view if it contains enough items.
You can wrap the StackPanel in a ScrollViewer, but i would suggest you to use a Grid instead
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox x:Name="ListOne"/>
<ListBox x:Name="ListTwo" Grid.Row="1"/>
</Grid>

Databound Triangle Creation?

I've got a requirement to create several shapes based on a supplied size (all of them have the same height/width) and have their sizes be databound to that supplied property on the datacontext.
Most of the shapes are easy: Circle (ellipse with height/width bound), square (rectangle with height/width bound), diamond (same as square, then use a RotateTransform), + (two lines), X (two lines).
But I'm trying to figure out how to do it for a triangle and I can't figure it out. It needs to be a filled object, so I can't just do it with three lines.
But all of the ways i've seen to do it (w/ a Path or a Polygon) end up taking Point objects (StartPoint, EndPoint, etc). And you can't bind to the X or Y values of the Point object.
Am I missing something? Or do I need to write my own custom shape or something?
Edit: To add a little bit of clarity... the type of triangle I'm creating doesn't really matter. It can be equilateral or isosceles. I was targeting an isosceles, so that it would have a base with the databound width and the top "tip" of the triangle will be at the mid-point of the databound width and at Y=0. That was just an optimization for simplicity's sake
The behavior class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;
namespace WpfApplication1
{
public enum ShapeType
{
Rectangle,
Isosceles,
Ellipse,
Dice,
Hexagon
}
public class PathControl
{
public static readonly DependencyProperty ShapeTypeProperty =
DependencyProperty.RegisterAttached("ShapeType",
typeof(ShapeType?),
typeof(DependencyObject),
new PropertyMetadata(null,
new PropertyChangedCallback((sender, args) =>
{
Path path = sender as Path;
ShapeType? shapeType = (ShapeType?)args.NewValue;
//todo: use a WeakEvent
path.SizeChanged +=
(pathSender, pathArgs) =>
{
PathControl.InvalidatePath((Path)sender, shapeType);
};
})));
private static void InvalidatePath(Path path, ShapeType? shapeType)
{
if (path != null
&& shapeType.HasValue)
{
string source = null;
double netWidth = path.Width - 2 * path.StrokeThickness,
netHeight = path.Height - 2 * path.StrokeThickness;
if (shapeType == ShapeType.Rectangle)
{
source = string.Format("M0,0 h{0} v{1} h-{0} z",
new object[2]
{
netWidth,
netHeight
});
}
else if (shapeType == ShapeType.Isosceles)
{
source = string.Format("M0,{1} l{0},-{1} {0},{1} z",
new object[2]
{
netWidth / 2,
netHeight
});
}
else
{
throw new NotImplementedException(shapeType.ToString());
}
path.Data = Geometry.Parse(source);
}
}
public static void SetShapeType(DependencyObject o, ShapeType e)
{
o.SetValue(PathControl.ShapeTypeProperty, e);
}
public static ShapeType? GetShapeType(DependencyObject o)
{
return (ShapeType)o.GetValue(PathControl.ShapeTypeProperty);
}
}
}
The XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication1">
<Grid>
<Path Width="100" Height="100" Stroke="Green" StrokeThickness="2" Fill="Yellow"
local:PathControl.ShapeType="Isosceles">
<Path.RenderTransform>
<RotateTransform Angle="90"></RotateTransform>
</Path.RenderTransform>
</Path>
</Grid>
</Window>
Binding to the Points is the best/only way. The X and X properties of a Point cannot be bound to because they do not raise the PropertyChanged event. The Point is a structure and structures should be read-only.
The PointCollection class raises the correct events so you can bind to it. This allows you to manipulate the triangles by modifying the collection of point by replacing the points. Do not change the point but replace them so the proper events will be raised.

Center WPF RibbonWindow Title via XAML Code

I've found some info on StackOverflow regarding my problem, so I introduced the following XAML code to my window.
Now everything is fine, while the WPF window hasn't quick launch icons or contextual tabs active.
Is there a way to center the application title completely via XAML Code.
<ribbon:Ribbon.TitleTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center" HorizontalAlignment="Stretch"
Width="{Binding ElementName=Window, Path=ActualWidth}">ApplicationTitle
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="0" Color="MintCream " BlurRadius="10"/>
</TextBlock.Effect>
</TextBlock>
</DataTemplate>
</ribbon:Ribbon.TitleTemplate>
Here's a very naïve way to do it. It comes about from inspecting the visual tree of a RibbonWindow and its concomitant Ribbon. I've been playing with this code for a couple of hours (and no longer) -- it's a bit rough around the edges and I'm not sure it's completely bug free. There's some optimizations to be made and it should be noted that I suck at WPF; there might be better way to do things.
For what it's worth the code is below, but note first:
The references to the PART_Icon template are not directly related to your question, but it is related to the aesthetics of the window.
The references to IsWin8OrHigher and FindChild are in classes that I'll include at the end. My interest in Windows 8 is that the native ribbon library centres the title text, whereas earlier versions of Windows do not. I'm trying to emulate that here.
I have no idea how the RibbonWindow was shipped with Visual Studio 2012 in its current iteration. The rendering on Windows 8 looks pretty miserable. After all this, I'm tempted to overload TitleTemplate with a TextBlock to get rid of the default glow and leave it at that.
The RibbonWindow doesn't look very good maximized, customization or not.
When I started writing this code, this was approximately what I was aiming for:
For comparison, this is how the RibbonWindow renders itself with no customisation:
This is how it renders with TitleTemplate defined to a TextBlock with TextAlignment="Center" but otherwise without any fancy text effects:
With the code below, we get this result:
MainWindow:
public partial class MainWindow {
public MainWindow() {
InitializeComponent();
if (Environment.OSVersion.IsWin8OrHigher()) {
SizeChanged += (sender, args) => TitleHack();
Activated += (sender, args) => TitleHack();
}
}
public override void OnApplyTemplate() {
base.OnApplyTemplate();
if (!Environment.OSVersion.IsWin8OrHigher())
return;
var icon = GetTemplateChild("PART_Icon") as Image;
if (icon == null)
return;
icon.Margin = new Thickness(icon.Margin.Left + 3, icon.Margin.Top + 2,
icon.Margin.Right, icon.Margin.Bottom);
}
private void TitleHack() {
var ribbonTitlePanel = MyRibbon.FindChild<FrameworkElement>("PART_TitlePanel");
var qatTopHost = MyRibbon.FindChild<FrameworkElement>("QatTopHost");
var titleHost = MyRibbon.FindChild<FrameworkElement>("PART_TitleHost");
var tabGroup = MyRibbon.FindChild<FrameworkElement>("PART_ContextualTabGroupItemsControl");
var qatTopHostLeft = qatTopHost.TransformToAncestor(ribbonTitlePanel).Transform(new Point(0, 0)).X;
var tabGroupLeft = tabGroup.TransformToAncestor(ribbonTitlePanel).Transform(new Point(0, 0)).X;
var width = ribbonTitlePanel.ActualWidth;
if (tabGroup.Visibility == Visibility.Visible) {
width -= tabGroup.ActualWidth;
width -= tabGroupLeft - qatTopHostLeft;
} else {
width -= qatTopHost.ActualWidth;
}
if (ResizeMode != ResizeMode.NoResize && WindowStyle != WindowStyle.None)
width -= 48; // For the min and max buttons
titleHost.Width = width > 0 ? width : Double.NaN;
}
}
OperatingSystemExtensionMethods.cs:
public static class OperatingSystemExtensionMethods {
private static readonly Version Windows8Version = new Version(6, 2);
public static bool IsWin8OrHigher(this OperatingSystem that) {
if (that.Platform != PlatformID.Win32NT)
return false;
return that.Version.CompareTo(Windows8Version) >= 0;
}
}
DependencyObjectExtensionMethods.cs:
public static class DependencyObjectExtensionMethods {
public static T FindChild<T>(this DependencyObject that, string elementName)
where T : FrameworkElement {
var childrenCount = VisualTreeHelper.GetChildrenCount(that);
for (var i = 0; i < childrenCount; i++) {
var child = VisualTreeHelper.GetChild(that, i);
var frameworkElement = child as FrameworkElement;
if (frameworkElement != null && elementName == frameworkElement.Name)
return (T) frameworkElement;
if ((frameworkElement = frameworkElement.FindChild<T>(elementName)) != null)
return (T) frameworkElement;
}
return null;
}
}
That should be working fine. I've just tested it and the title centers as it should.
if you want it truly centered, it needs to be:
HorizontalAlignment="Center"

WPF RichTextBox with no width set

I have the following XAML code:
<Window x:Class="RichText_Wrapping.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<Grid>
<RichTextBox Height="100" Margin="2" Name="richTextBox1">
<FlowDocument>
<Paragraph>
This is a RichTextBox - if you don't specify a width, the text appears in a single column
</Paragraph>
</FlowDocument>
</RichTextBox>
</Grid>
... If you create this window in XAML, you can see that when you don't specify a width for the window, it wraps the text in a single column, one letter at a time. Is there something I'm missing? If it's a known deficiency in the control, is there any workaround?
This is a confirmed bug with the WPF RichTextBox. To fix it, Bind the PageWidth of the FlowDocument to the RichTextBox width, i.e.
<RichTextBox Name="rtb">
<FlowDocument Name="rtbFlowDoc" PageWidth="{Binding ElementName=rtb, Path=ActualWidth}" />
</RichTextBox>
EDIT:
Give the FlowDocument a name so that you can access it in the code behind and never new the flow document in codebehind.
Try binding the FlowDocument's width (one way) to the width of the container RichTextBox.
Worked for me...
The approach in this article worked for me:
WPF RichTextBox doesn't provide the functionality to adjust its width
to the text. As far as I know, RichTextBox use a FlowDocumentView in
its visual tree to render the Flowdocument. It will take the available
space to render its content, so it won't adjust its size to the
content. Since this is an internal class, it seems we cannot override
the layout process to let a RichTextBox to adjust its size to the
text.
Therefore, I think your approach is in the right direction.
Unfortunelately, based on my research, there is no straightforward way
to measure the size of the rendered text in a RichTextBox.
There is a workaround we can try. We can loop through the flowdocument
in RichTextBox recursively to retrieve all Run and Paragraph objects.
Then we convert them into FormattedText to get the size.
This article demonstrates how to convert a FlowDocument to
FormattedText. I also write a simple sample using the
FlowDocumentExtensions class in that article.
public Window2()
{
InitializeComponent();
StackPanel layoutRoot = new StackPanel();
RichTextBox myRichTextBox = new RichTextBox() { Width=20};
this.Content = layoutRoot;
layoutRoot.Children.Add(myRichTextBox);
myRichTextBox.Focus();
myRichTextBox.TextChanged += new TextChangedEventHandler((o,e)=>myRichTextBox.Width=myRichTextBox.Document.GetFormattedText().WidthIncludingTrailingWhitespace+20);
}
public static class FlowDocumentExtensions
{
private static IEnumerable<TextElement> GetRunsAndParagraphs(FlowDocument doc)
{
for (TextPointer position = doc.ContentStart;
position != null && position.CompareTo(doc.ContentEnd) <= 0;
position = position.GetNextContextPosition(LogicalDirection.Forward))
{
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd)
{
Run run = position.Parent as Run;
if (run != null)
{
yield return run;
}
else
{
Paragraph para = position.Parent as Paragraph;
if (para != null)
{
yield return para;
}
}
}
}
}
public static FormattedText GetFormattedText(this FlowDocument doc)
{
if (doc == null)
{
throw new ArgumentNullException("doc");
}
FormattedText output = new FormattedText(
GetText(doc),
CultureInfo.CurrentCulture,
doc.FlowDirection,
new Typeface(doc.FontFamily, doc.FontStyle, doc.FontWeight, doc.FontStretch),
doc.FontSize,
doc.Foreground);
int offset = 0;
foreach (TextElement el in GetRunsAndParagraphs(doc))
{
Run run = el as Run;
if (run != null)
{
int count = run.Text.Length;
output.SetFontFamily(run.FontFamily, offset, count);
output.SetFontStyle(run.FontStyle, offset, count);
output.SetFontWeight(run.FontWeight, offset, count);
output.SetFontSize(run.FontSize, offset, count);
output.SetForegroundBrush(run.Foreground, offset, count);
output.SetFontStretch(run.FontStretch, offset, count);
output.SetTextDecorations(run.TextDecorations, offset, count);
offset += count;
}
else
{
offset += Environment.NewLine.Length;
}
}
return output;
}
private static string GetText(FlowDocument doc)
{
StringBuilder sb = new StringBuilder();
foreach (TextElement el in GetRunsAndParagraphs(doc))
{
Run run = el as Run;
sb.Append(run == null ? Environment.NewLine : run.Text);
}
return sb.ToString();
}
}
I copy pasted your code and its not in a single column, Do you have a width somewhere that is small? Maybe defined on the code behind for instance.
I noticed that I only had this issue when my default ScrollViewer style explicitly set HorizontalScrollBarVisibility=Hidden. Removing this setter (default value is Hidden anyway) fixed the single column issue for me in my RichTextBox.
Just for the record as I think this thread is missing some explanations as per the why: RichTextBox MeasureOverride implementation is like that. I won't call that a bug, maybe just a poor design behavior justified by the fact that just like mentioned above the FlowDocument is not cheap to measure due to its complexity. Bottom line, avoid unlimited Width constraint by binding MinWidth or wrap it in a limiting container.
/// <summary>
/// Measurement override. Implement your size-to-content logic here.
/// </summary>
/// <param name="constraint">
/// Sizing constraint.
/// </param>
protected override Size MeasureOverride(Size constraint)
{
if (constraint.Width == Double.PositiveInfinity)
{
// If we're sized to infinity, we won't behave the same way TextBox does under
// the same conditions. So, we fake it.
constraint.Width = this.MinWidth;
}
return base.MeasureOverride(constraint);
}

Resources