WPF TextBlock get lines after textwrapping - wpf

I have FixedDocument page and I want to place TextBlock on it, but it can be that Textblock doesn't fit on page by height.
So I want to take lines from generated TextBlock with TextWrapping, and then create new TextBlock, that fitted by height and place it on page.
TextBlock have LineCount private property, that mean that it has TextLines after wrapping and I can somehow get it.
Creating TextBlock with runs:
public TextItem(PageType pageType, Run[] runs, Typeface typeFace, double fontSize)
: base(pageType)
{
this.TextBlock = new TextBlock();
this.TextBlock.Inlines.AddRange(runs);
if (typeFace != null)
this.TextBlock.FontFamily = typeFace.FontFamily;
if (fontSize > 0)
this.TextBlock.FontSize = fontSize;
this.TextBlock.TextWrapping = TextWrapping.Wrap; //wrapping
}
Creating TextBlock with text:
public TextItem(PageType pageType, String text, Typeface typeFace, double fontSize)
: base(pageType)
{
if (typeFace == null || fontSize == 0)
throw new Exception("Wrong textitem parameters");
this.TextBlock = new TextBlock();
this.TextBlock.Text = text;
this.TextBlock.FontFamily = typeFace.FontFamily;
this.TextBlock.FontSize = fontSize;
this.TextBlock.TextWrapping = TextWrapping.Wrap;
this.TextBlock.TextAlignment = TextAlignment.Justify;
this.TypeFace = typeFace;
}
Set width to TextBlock and get DesiredSize :
this.TextBlock.Width = document.CurrentPage.Content.ActualWidth;
this.TextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

I faced the exactly same problem, and for some time, I lost the hope and I thought there is no solution for this.
BUT, I was wrong, there are many solutions for that (At least three)
And you are right, one of them use the LineCount property by using reflection.
And the second using it is own algorithm to get the lines.
And the third, which is preferred to me, has very elegant way to get the result which you want.
Please refer to this question, to see the three answers of this.
Get the lines of the TextBlock according to the TextWrapping property?
Here is a copy of the best solution (in my opinion)
public static class TextUtils
{
public static IEnumerable<string> GetLines(this TextBlock source)
{
var text = source.Text;
int offset = 0;
TextPointer lineStart = source.ContentStart.GetPositionAtOffset(1, LogicalDirection.Forward);
do
{
TextPointer lineEnd = lineStart != null ? lineStart.GetLineStartPosition(1) : null;
int length = lineEnd != null ? lineStart.GetOffsetToPosition(lineEnd) : text.Length - offset;
yield return text.Substring(offset, length);
offset += length;
lineStart = lineEnd;
}
while (lineStart != null);
}
}

Related

listview.ScrollIntoView(listview.SelectedItem) is not working when repeated rows occurs in listview

WPF listview.ScrollIntoView(listview.SelectedItem) doesn't work, if items contain reference duplicates (repeated rows occur).
I am working on FINDNEXT functionality.
In my listview rows data are repeated. When repeated row appears it scrolls to the first occurrence of the duplicate.
How to scroll to the right item in ListView?
private void Listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
listview.UpdateLayout();
listview.ScrollIntoView(listview.SelectedItem);
}
This solution needs the virtualisation be switched off, better way as Clemens offered - clone a duplicates for they are different objects, then you can use your approach.
private static DependencyObject FirstVisualChild(UIElement visual)
{
if (visual == null)
return null;
if (VisualTreeHelper.GetChildrenCount(visual) == 0)
return null;
return VisualTreeHelper.GetChild(visual, 0);
}
private void Listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var itmFromIdx = listView.ItemContainerGenerator.ContainerFromIndex(listView.SelectedIndex);
ScrollContentPresenter presenter = null;
for (var vis = itmFromIdx as UIElement; vis != null; vis = VisualTreeHelper.GetParent(vis) as UIElement)
if ((presenter = vis as ScrollContentPresenter) != null)
break;
if (presenter == null)
throw new Exception();
var virtStackPnl = FirstVisualChild(presenter.Content as ItemsPresenter);//VirtualizingStackPanel
(virtStackPnl as IScrollInfo).MakeVisible(itmFromIdx as UIElement, new Rect(new Size(0, 0)));
}
XAML:
<ListView x:Name="listView" ... VirtualizingPanel.IsVirtualizing="False">

An unhandled exception of type 'System.InvalidCastException' in winform

I have project that I click Button and it's will create a new PictureBox (pb) on a PictureBox1. And when I choose item on a combobox and PictureBox (pb) will appear in the position I want and the problem appears. How can I fix it or use "pb" in void comboBox3_SelectedIndexChanged. Thank you.
private void btaddagv_Click(object sender, EventArgs e)
{
AddNewPictureBox();
}
public System.Windows.Forms.PictureBox AddNewPictureBox()
{
System.Windows.Forms.PictureBox pb = new System.Windows.Forms.PictureBox();
pictureBox1.Controls.Add(pb);
pb.Name = "STT" + tbAdd.Text;
pb.Image = Image.FromFile("AGV-1.jpg");
pb.Height = 30;
pb.Width = 40;
pb.SizeMode = PictureBoxSizeMode.Zoom;
pb.Location = new System.Drawing.Point(tdx, 500);
tdx = tdx + 200;
return pb;
}
private void comboBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if(comboBox3.SelectedItem == "A")
{
PictureBox pb = (PictureBox)sender;
pb.Location = lbA.Location;
}
}
And here is an error
If we make an assumption on this line:
pb.Name = "STT" + tbAdd.Text;
that tbAdd.Text contains the A, B, C, D etc that you're checking for here:
if(comboBox3.SelectedItem == "A")
Then your SelectedIndexChanged event handler should be:
private void comboBox3_SelectedIndexChanged(object sender, EventArgs e)
{
string selected = comboBox3.SelectedItem.ToString();
if (!string.IsNullOrWhitespace(selected))
{
PictureBox pb = picturebox1.Controls.OfType<PictureBox>().Where(p => p.Name == $"STT{selected}").FirstOrDefault();
if (pb != null)
{
Label lb = picturebox1.Controls.OfType<Label>().Where(l => l.Name == $"lb{selected}").FirstOrDefault();
if (lb != null)
pb.Location = lb.Location;
}
}
}
I've made some extra assumptions here. First I've assumed that the "lbA" control you're referring to is a Label. The second assumption is that lbA and the other controls are all share the same parent (picturebox1)
What the above is doing is getting the value of the SelectedItem (if we use your example with the letter A) then attempts to find any PictureBox controls that have the name STTA. If it finds one it looks for a Label on the same parent called lbA. If that exists then it moves the picturebox control to the location of the label.

How to get the child control inside a TreeviewItem?

I customed my TreeViewItem to be a StackPanel with image and textblock inside; I'd like to get a reference to the TextBlock inside. For the codes below node is of type TreeviewItem and I am surechildrenCound =3 which could be StackPanel image textblock! But it can not find any TextBlock inside. I never see any console output and object _itemToMovereturns null
TreeViewItem node = UIHelper.FindVisualParent<TreeViewItem>(e.OriginalSource as FrameworkElement);
var child = VisualTreeHelper.GetChild(node, 0);
int childrenCount = VisualTreeHelper.GetChildrenCount(child);
for (int i = 0; i < childrenCount; i++)
{
TextBlock vc = VisualTreeHelper.GetChild(child, i) as TextBlock;
if (vc != null)
{
Console.WriteLine("ggggggggggggggggggggggggggggggggggggggggggggggg");
_itemToMove = vc.Text as object;
}
}
Console.WriteLine(childrenCount+";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
It may be that your TextBlock is buried deeper than you think. I've always had success using the following helper which is generic enough to be used elsewhere in the app.
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
if (obj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
I think I got this from a similar question from StackOverflow.
As Michael T said, TreeViewItem is also a child of a TreeViewItem. I ran into this old question because I wanted only the visualtree elements of a single TreeViewItem and the so many times used by me FindVisualChild function wasn't this time useful.
So I modified this function to retrieve elements of TreeVieItem excluding the TreeViewItems inside. The function is based on the fact that any hierarchy is defined by nodes, and asumed that a node has a NodeType that is not the type of the children. However, you can go deep in the tree as many levels as you want (HierachyLevels param).
The function also search for specific type and name of the elements.
Public Sub FindChildGroup(Of T As DependencyObject, H As DependencyObject)(Parent As DependencyObject _
, ChildName As String _
, ByRef OutputList As List(Of T) _
, Optional HierachyLevels As Integer = 0)
Dim childrenCount As Integer = VisualTreeHelper.GetChildrenCount(Parent)
For i As Integer = 0 To childrenCount - 1
'Analyze child
Dim child = VisualTreeHelper.GetChild(Parent, i)
'Is node?
Dim IsNode = TypeOf child Is H
If IsNode And HierachyLevels > 0 Or TypeOf child IsNot H Then
Dim child_Test As T = TryCast(child, T)
If child_Test IsNot Nothing Then
'should be included in the list?
Dim child_Element As FrameworkElement = TryCast(child_Test, FrameworkElement)
If child_Element.Name Like ChildName Then
OutputList.Add(child_Test)
End If
End If
'Go down next level
If TypeOf child Is H Then HierachyLevels -= 1
FindChildGroup(Of T, H)(child, ChildName, OutputList, HierachyLevels)
If TypeOf child Is H Then HierachyLevels += 1
End If
Next
End Sub

GetPositionAtOffset equivalent for text only?

Is there a pretty solution of a GetPositionAtOffset() equivalent which only counts text insertion positions instead of all symbols?
Motivation example in C#:
TextRange GetRange(RichTextBox rtb, int startIndex, int length) {
TextPointer startPointer = rtb.Document.ContentStart.GetPositionAtOffset(startIndex);
TextPointer endPointer = startPointer.GetPositionAtOffset(length);
return new TextRange(startPointer, endPointer);
}
Edit: Until now i "solved" it this way
public static TextPointer GetInsertionPositionAtOffset(this TextPointer position, int offset, LogicalDirection direction)
{
if (!position.IsAtInsertionPosition) position = position.GetNextInsertionPosition(direction);
while (offset > 0 && position != null)
{
position = position.GetNextInsertionPosition(direction);
offset--;
if (Environment.NewLine.Length == 2 && position != null && position.IsAtLineStartPosition) offset --;
}
return position;
}
As far as I'm aware, there isn't. My suggestion is that you create your own GetPositionAtOffset method for this purpose. You can check which PointerContext the TextPointer is adjacent to by using:
TextPointer.GetPointerContext(LogicalDirection);
To get the next TextPointer which points to a different PointerContext:
TextPointer.GetNextContextPosition(LogicalDirection);
Some sample code I used in a recent project, this makes sure that the pointer context is of type Text, by looping until one is found. You could use this in your implementation and skip an offset increment if it is found:
// for a TextPointer start
while (start.GetPointerContext(LogicalDirection.Forward)
!= TextPointerContext.Text)
{
start = start.GetNextContextPosition(LogicalDirection.Forward);
if (start == null) return;
}
Hopefully you can make use of this information.
Could not find effective solution to this problem for a long time.
Next piece of code works in my case with the highest performance. Hope it will help somebody as well.
TextPointer startPos = rtb.Document.ContentStart.GetPositionAtOffset(searchWordIndex, LogicalDirection.Forward);
startPos = startPos.CorrectPosition(searchWord, FindDialog.IsCaseSensitive);
if (startPos != null)
{
TextPointer endPos = startPos.GetPositionAtOffset(textLength, LogicalDirection.Forward);
if (endPos != null)
{
rtb.Selection.Select(startPos, endPos);
}
}
public static TextPointer CorrectPosition(this TextPointer position, string word, bool caseSensitive)
{
TextPointer start = null;
while (position != null)
{
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
string textRun = position.GetTextInRun(LogicalDirection.Forward);
int indexInRun = textRun.IndexOf(word, caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase);
if (indexInRun >= 0)
{
start = position.GetPositionAtOffset(indexInRun);
break;
}
}
position = position.GetNextContextPosition(LogicalDirection.Forward);
}
return start; }

Is there a way to get all BindingExpression objects for a Window?

Is there a way to get all BindingExpression objects for a Window?
I am trying to refresh the form when the number PropertyChanged events that need to be fired to refresh a form is too high and not a good option. I am thinking doing it the other way that the form/window can re-query all bindings.
If you raise PropertyChanged with the PropertyChangedEventArgs that have a parameter of null or String.Empty the bindings of all properties will update.
[MSDN Reference]
Doing it the other way around is a lot more complicated and probably more performance consuming i think. You would need to check every DependencyProperty of every DependencyObject in the whole window for bindings.
Edit: Wrote the following sketchy extension method which does what you asked for, it's awfully inefficient (there is probably room for improvement but you're still dealing with an algorithm of considerable complexity):
public static void UpdateAllBindings(this DependencyObject o)
{
//Immediate Properties
List<FieldInfo> propertiesAll = new List<FieldInfo>();
Type currentLevel = o.GetType();
while (currentLevel != typeof(object))
{
propertiesAll.AddRange(currentLevel.GetFields());
currentLevel = currentLevel.BaseType;
}
var propertiesDp = propertiesAll.Where(x => x.FieldType == typeof(DependencyProperty));
foreach (var property in propertiesDp)
{
BindingExpression ex = BindingOperations.GetBindingExpression(o, property.GetValue(o) as DependencyProperty);
if (ex != null)
{
ex.UpdateTarget();
}
}
//Children
int childrenCount = VisualTreeHelper.GetChildrenCount(o);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(o, i);
child.UpdateAllBindings();
}
}
Just for reference, WPF itself does exactly this (iterates through all the data bound properties) when you call BindingOperations.ClearAllBindings().
The code for that is the following:
public static void ClearAllBindings(DependencyObject target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
LocalValueEnumerator localValueEnumerator = target.GetLocalValueEnumerator();
ArrayList arrayList = new ArrayList(8);
while (localValueEnumerator.MoveNext())
{
LocalValueEntry current = localValueEnumerator.Current;
if (BindingOperations.IsDataBound(target, current.Property))
{
arrayList.Add(current.Property);
}
}
for (int i = 0; i < arrayList.Count; i++)
{
target.ClearValue((DependencyProperty)arrayList[i]);
}
}
LocalValueEnumerator is public so you can use it too.
You should be able to deduce the solution from this easily.

Resources