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
Related
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">
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.
I was wondering if it was possible with a TreeView in a windows form to add or remove a level?
For example:
my treeview is like this to begin with:
ParentNode
| Child1
| Child2
if user clicks on a button to add a level to Child2 it becomes:
ParentNode
| Child1
| | Child1.1
There is a a Node.Level function but only usable to get the level and not to set it.
EDIT:
The nodes are built automatically, the level is assigned depending on the style of an excel cell. The problem is, it happens that the created node is not at it's correct place because the excel file is not well made. So I see 2 options o resolve this problem:
1- the user modifies the excel file directly
2- I create a Move Left Move Right button on a selection of nodes.
I'd like to offer the 2nd possibility.
Here's the code I used to build the nodes:
public static void AddNodes(Excel.Application app,
TreeView treeView)
{
Excel.Range selection = app.Selection;
ArrayList style = new ArrayList();
TreeNode parentNode = treeView.SelectedNode;
//Selected Node => Last used node
for (int i = 1; i <= selection.Rows.Count; i++)
{
TreeNode tn;
int fontSize = Convert.ToInt32(selection.Cells[i].Font.Size);
if (!style.Contains(fontSize))
{
style.Add(fontSize);
}
else if (style[style.Count - 1].Equals(fontSize))
{
try
{
treeView.SelectedNode = treeView.SelectedNode.Parent;
}
catch (Exception x)
{
ErrorBox(x);
}
}
else
{
int indexPreviousCellofSameColor = style.IndexOf(fontSize);
//Select TN parent
for (int j = 1; j <= (style.Count - indexPreviousCellofSameFont); j++)
{ treeView.SelectedNode = treeView.SelectedNode.Parent; }
style.RemoveRange(indexPreviousCellofSameFont + 1, style.Count - indexPreviousCellofSameFont - 1);
}
if (selection.Cells[i].Value2 == null)
{
//if empty cell, do something ... or nothing
treeView.SelectedNode = treeView.SelectedNode.LastNode;
}
else
{
//Add new TN to parent - TN object corresponds to excel cell
tn = new TreeNode()
{
Text = selection.Cells[i].Value2,
Tag = selection.Cells[i],
};
treeView.SelectedNode.Nodes.Add(tn);
tn.ToolTipText = tn.Level.ToString();
//selected TN => created TN
treeView.SelectedNode = tn;
}
}
}
I had to change my answer completely to the changed question.
This seems to do the job in my tests. It moves the selected node to a new level, under the one that was just above it.
It needs more checks offcourse to make sure your not moving nodes to oblivion...
private void button1_Click(object sender, EventArgs e)
{
TreeNode selected = treeViewFilter.SelectedNode;
TreeNode parent = selected.Parent;
// find the node just above the selected node
TreeNode prior = parent.Nodes[selected.Index - 1];
if (parent != prior)
{
treeViewFilter.Nodes.Remove(selected);
prior.Nodes.Add(selected);
}
}
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);
}
}
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.