WPF default TreeView is scrolled to bottom of the node automatically where as we need to show the top view of the tree view. How to do that?
Also I could not get the scroll viewer by walking down the Visual Tree.
Preselect top node and call TreeViewItem.BringIntoView method on selection changed event. Call TreeView.ItemContainerGenerator.ContainerFromItem(e.NewValue) to get hold of the TreeViewItem.
This code is very rough.
The key to getting the TreeViewItem.BringIntoView() to get an item to the top, is to first scroll the TreeView to the bottom rather than the top.
To do this, we need to access the ScrollViewer inside the TreeView's control template first. Lots of messing around IMO, that should have been provided in the framework from the outset.
Your item control in this case, should be your TreeViewItem that you are trying to get to the top.
The uxTree control is the TreeView.
item.IsSelected = true;
ScrollViewer scroller = (ScrollViewer)this.FindVisualChildElement(this.uxTree, typeof(ScrollViewer));
scroller.ScrollToBottom();
item.BringIntoView();
private FrameworkElement FindVisualChildElement(DependencyObject element, Type childType)
{
int count = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < count; i++)
{
var dependencyObject = VisualTreeHelper.GetChild(element, i);
var fe = (FrameworkElement)dependencyObject;
if (fe.GetType() == childType)
{
return fe;
}
FrameworkElement ret = null;
if (fe.GetType().Equals(typeof(ScrollViewer)))
{
ret = FindVisualChildElement((fe as ScrollViewer).Content as FrameworkElement, childType);
}
else
{
ret = FindVisualChildElement(fe, childType);
}
if (ret != null)
{
return ret;
}
}
return null;
}
Related
I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.
I need to find all TextBox(es) that are on a UWP Page but having no luck. I thought it would be a simple foreach on Page.Controls but this does not exist.
Using DEBUG I am able to see, for example, a Grid. But I have to first cast the Page.Content to Grid before I can see the Children collection. I do not want to do this as it may not be a Grid at the root of the page.
Thank you in advance.
UPDATE: This is not the same as 'Find all controls in WPF Window by type'. That is WPF. This is UWP. They are different.
You're almost there! Cast the Page.Content to UIElementCollection, that way you can get the Children collection and be generic.
You'll have to make your method recurse and look either for Content property if element is a UIElement or Children if element is UIElementCollection.
Here's an example:
void FindTextBoxex(object uiElement, IList<TextBox> foundOnes)
{
if (uiElement is TextBox)
{
foundOnes.Add((TextBox)uiElement);
}
else if (uiElement is Panel)
{
var uiElementAsCollection = (Panel)uiElement;
foreach (var element in uiElementAsCollection.Children)
{
FindTextBoxex(element, foundOnes);
}
}
else if (uiElement is UserControl)
{
var uiElementAsUserControl = (UserControl)uiElement;
FindTextBoxex(uiElementAsUserControl.Content, foundOnes);
}
else if (uiElement is ContentControl)
{
var uiElementAsContentControl = (ContentControl)uiElement;
FindTextBoxex(uiElementAsContentControl.Content, foundOnes);
}
else if (uiElement is Decorator)
{
var uiElementAsBorder = (Decorator)uiElement;
FindTextBoxex(uiElementAsBorder.Child, foundOnes);
}
}
Then you call that method with:
var tb = new List<TextBox>();
FindTextBoxex(this, tb);
// now you got your textboxes in tb!
You can also use the following generic method from the VisualTreeHelper documentation to get all your child controls of a given type:
internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
{
T asType = (T)current;
results.Add(asType);
}
FindChildren<T>(results, current);
}
}
It basically recursively get the children for the current item and add any item matching the requested type to the provided list.
Then, you just have to do the following somewhere to get your elements:
var allTextBoxes = new List<TextBox>();
FindChildren(allTextBoxes, this);
To my mind, you could do it in the same way as in WPF. Because UWP uses mostly the same XAML that WPF.
So, please check out answer for the same question about WPF
The Setting:
I have a RichTextBox containing a hyperink and a DropDownButton somewhere else in my UI. Now when I click the button's DropDown open and afterwards click somewhere else on my UI, the DropDown is implemented to close, and check if it still owns the keyboardfocus so it can set its ToggleButton to focused again after the DropDown collapsed as intended.
The Problem:
When clicking inside my RichTextBox I will face an InvalidOperationException caused by my method to check focus ownership. The call to VisualTreeHelper.GetParent(potentialSubControl) works fine for all elements that are part of the VisualTree. Apparently the focused Hyperlink (returned by FocusManager.GetFocusedElement()) is not part of the VisualTree and therefore is invalid input to GetParent(). Well, how can I find the parent (either logical parent or visual parent) of a hyperlink within my RichTextBox?
My method for determining focus ownership:
// inside DropDownButton.cs
protected override void OnLostFocus( RoutedEventArgs e )
{
base.OnLostFocus( e );
if (CloseOnLostFocus && !DropDown.IsFocused()) CloseDropDown();
}
// inside static class ControlExtensions.cs
public static bool IsFocused( this UIElement control )
{
DependencyObject parent;
for (DependencyObject potentialSubControl =
FocusManager.GetFocusedElement() as DependencyObject;
potentialSubControl != null; potentialSubControl = parent)
{
if (object.ReferenceEquals( potentialSubControl, control )) return true;
try { parent = VisualTreeHelper.GetParent(potentialSubControl); }
catch (InvalidOperationException)
{
// can happen when potentialSubControl is technically
// not part of the visualTree
// for example when FocusManager.GetFocusedElement()
// returned a focused hyperlink (System.Windows.Documents.Hyperlink)
// from within a text area
parent = null;
}
if (parent == null) {
FrameworkElement element = potentialSubControl as FrameworkElement;
if (element != null) parent = element.Parent;
}
}
return false;
}
[Edit]
One potential idea to solve the issue: since Hyperlink is a DependencyObject I could try to access its inheritance context and find other DependencyObjects higher up in the tree and test them for being FrameworkElements. But I struggle to find any information about inheritance context in Silverlight.
If I have
<StackPanel>
<Button name="btn"/>
</StackPanel>
how could I replace Button btn for a different control when I press that button? In this case I know the parent is a StackPanel so I could cast the parent to a stackPanel and then replace that child. but what if I have a Border?
<Border>
<Button name="btn"/>
</Border>
Thanks to McGarnagle I created this extension method
how can I replace a Button with my custom control without knowing in advance the parent of the button?
Edit
public static void ReplaceWith(this FrameworkElement elementToReplace, FrameworkElement newControl)
{
newControl.Width = elementToReplace.Width;
newControl.Height = elementToReplace.Height;
newControl.Margin = elementToReplace.Margin;
// get parent of control
var parent = elementToReplace.Parent;
if (parent is Panel)
{
var panel = (Panel)parent;
for (var i = 0; i < panel.Children.Count; i++)
{
if (panel.Children[i] == elementToReplace)
{
panel.Children.RemoveAt(i);
panel.Children.Insert(i, newControl);
break;
}
}
}
else if (parent is Decorator)
{
((Decorator)parent).Child = newControl;
}
else if (parent is ContentControl)
{
((ContentControl)parent).Content = newControl;
}
else
{
if(Debugger.IsAttached)
Debugger.Break();
throw new NotImplementedException("Missing other possibilities to implement");
}
}
I suppose you could cycle through the possible base classes. There aren't that many: StackPanel is a Panel, Border is a Decorator, …
var parent = btn.Parent;
var replacement = new TextBlock { Text = "replacement" };
if (parent is Panel)
{
var panel = (Panel)parent;
panel.Children.Remove(btn);
panel.Children.Add(replacement);
}
else if (parent is Decorator)
{
((Decorator)parent).Child = replacement;
}
else if (parent is ContentControl)
{
((ContentControl)parent).Content = replacement;
}
All containers are Panel or ContentControl or UserControl. So you can check if parent is inherited from one of these controls.
Yo can get the logical parent of Control and check it's type using the operator is like this:
var parent = MyButton.Parent;
if (parent is StackPanel)
{
// do something with StackPanel
}
if (parent is Border)
{
// do something with Border
}
I am trying to get the text value of a "cell" inside of a GridView that is set as the view of a ListView. I do not want to get the SelectedItem of the ListView as that just returns my entire View Model (but not which property the cell refers to).
I am able to get the text value by responding to direct mouse events (up down or whatever) and if the value is a textblock, obviously I can use the text. This works great and as of right now this is my only solution, although its currently limited.
I would like to take it a step further and be able to click anywhere with in the cell area, navigate around to find the appropriate textblock and then use that value. I have tried a half million ways to do this but what seems logical doesn't seem to quite work out like it should.
Setup:
I have a dynamic GridView that creates its own columns and bindings based on data models that I pass to it. I am using a programmatic cell template (shown below) to have individual control over the cells, particularly so I can add a "border" to it making it actually separate out each cell. I have named the objects so I can access them easier when I'm navigating around the VisualTree.
Here is the Template Code. (Note that the content presenter originally was a textblock itself, but this was changed for later flexibility)
private DataTemplate GetCellTemplate(string bindingName)
{
StringBuilder builder = new StringBuilder();
builder.Append("<DataTemplate ");
builder.Append("xmlns='http://schemas.microsoft.com/winfx/");
builder.Append("2006/xaml/presentation' ");
builder.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
builder.Append("xmlns:local = 'clr-namespace:XXXXXXXX");
builder.Append(";assembly=XXXXXXXXX'>");
builder.Append("<Border Name=\"border\" BorderThickness=\"1,0,0,0\" BorderBrush=\"Gray\" Margin=\"-6,-3,-6,-3\">");
builder.Append("<Grid Margin=\"6,3,6,3\">");
builder.Append("<ContentPresenter Name=\"content\" HorizontalAlignment=\"Stretch\" Content=\"{Binding ");
builder.Append(string.Format("{0}", bindingName));
builder.Append("}\"/>");
builder.Append("</Grid>");
builder.Append("</Border>");
builder.Append("</DataTemplate>");
DataTemplate cellTemplate= (DataTemplate)XamlReader.Parse(builder.ToString());
return cellTemplate;
}
What I have Tried:
The logical approach for me was to react to a Mouse event. From the object that had the mouse event I would do either
A. Look at its children to find a textblock, or
B. Get its parent then look for child with a textblock.
My assumption is that if I click in white space I'm clicking in a container that has my textblock. So far the two things that come up are a Border and a Rectangle (if I don't click the text itself). A. Returns absolutely nothing except for the recangle and the border. When I do B i can find textblocks but they are every single text block in the entire row.
So what I try to do from that is get all textblocks, then go backwards till I find which one has a IsMouseOver property as true. It turns out none of these objects EVER have a IsMouseOver except the content presenter for the entire row. So this seems to indicate to me is that the whitespace in the cells does not actually contain the textblock.
What I find is that when I click on the Border and start looking at children, I eventually get to a container that has a rectangle (the rectangle I click) and a grid row view presenter. The presenter shows all of the objects inside the row (hence why i would get all textblocks when i do this recursive scan).
Here is some of the code used to do this to get an idea of what i'm doing. I have written about 10 different versions of this same recursive code generally attempting to find who has the Mouse over it and is related to a textbox.
private void OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
object original = e.OriginalSource;
if (original is TextBlock)
{
this.valueTextBlock.Text = ((TextBlock)original).Text;
}
else if (original is FrameworkElement)
{
var result = GetAllNestedChildren<Border>(VisualTreeHelper.GetParent((DependencyObject)original)).Where(x => x.Name == "border").Where(x => HasAChildWithMouse(x)).ToList();
}
else
{
this.valueTextBlock.Text = string.Empty;
}
}
private bool HasAChildWithMouse(UIElement element)
{
if (element.IsMouseOver || element.IsMouseDirectlyOver)
return true;
var childCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(element, i);
if (child is UIElement)
if (HasAChildWithMouse((UIElement)child))
return true;
}
return false;
}
private IEnumerable<T> GetAllNestedChildren<T>(DependencyObject obj) where T : UIElement
{
if (obj is T)
yield return obj as T;
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
foreach (var nested in GetAllNestedChildren<T>(child))
yield return nested;
}
}
private T GetObjectByTypeParentHasMouse<T>(DependencyObject obj) where T : UIElement
{
if (obj is T)
{
if ((VisualTreeHelper.GetParent(obj) as UIElement).IsMouseOver )
{
return obj as T;
}
}
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
var correctType = GetObjectByTypeParentHasMouse<T>(child);
if (correctType != null)
return correctType;
}
return null;
}
private T GetContainedType<T>(DependencyObject obj, bool checkForMouseOver) where T : UIElement
{
if (obj is T && ((T)obj).IsMouseOver)
return obj as T;
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
var correctType = GetContainedType<T>(child, checkForMouseOver);
if (correctType != null)
return correctType;
}
return null;
}
The other approach I took was to start with the TextBlock itself, find its containing parent and find out how i can navigate to the answer. I find the templateparent is the ContentPresenter (named ="content") I find the grid, and then the border. The parent of the border is a content presenter whos content is the data view model for the entire row. The parent of this contentpresenter is the grid column's presenter. This is the same one that i was navigating up to in the other one.
It would appear that the first approach objects while are contain the cell do not actually contain the textblock or the entire cell templated items. It would appear to me there is no way to go from the Border or Rectangle that is clicked, back to the actual text field.
"Long story short" is there ANY way to make this connection?
(Btw I am not willing to give up this ListView/GridView because its payoffs far outweigh this negative and I'd gladly give up on this idea to keep the rest).
I think you sjould be able to either
1) Add some kind of (toggle)button to the root of your data template, and either bind to Command and handle it on your viewmodel or bind to IsChecked/IsPressed and handle changes via data triggers or w/e on the view side.
2) Add EventTrigger to your datatemplate at some point, and handle PreviewNouseUp/Down events there via simple animations.