I am working on a project where i have to deal with navigation based on Frame in MVVM pattern thus to get to the element Name x:Name of type Frame we have to convert MainWindow into DependencyObject like this..
private static FrameworkElement GetDescendantFromName(DependencyObject parent, string name)
{
var count = VisualTreeHelper.GetChildrenCount(parent);
if (count < 1)
{
return null;
}
for (var i = 0; i < count; i++)
{
var frameworkElement = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;
if (frameworkElement != null)
{
if (frameworkElement.Name == name)
{
return frameworkElement;
}
frameworkElement = GetDescendantFromName(frameworkElement, name);
if (frameworkElement != null)
{
return frameworkElement;
}
}
}
return null;
}
In Navigation Service Class i use...
var frame = GetDescendantFromName(Application.Current.MainWindow, "FrameName") as Frame;
frame.source = new Uri("Views/StudentView.Xaml");
This technique is limited to only MainWindow. When i pass new instence of EmployeeDetailView.Xaml as a depenecy Object, The Xaml File is not loaded and GetChildrenCount() returns 0.
var frame = GetDescendantFromName(EmployeeDetaiView.Xaml, "FrameName") as Frame;
here frame has null value.
how could i make it work with currently rendered EmployeeDetailView to get the Frame element?
Use Application.LoadComponent Method (Uri)
Page p = (Page) Application.LoadComponent(new Uri("Views/EmployeeDetaiView.Xaml.xaml", UriKind.Relative));
var ctrl = GetDescendantFromName(p, "SomeControl");
Here, EmployeeDetaiView.Xaml lies in Views folder.
EDIT #1 after user comments
However, as pointed by OP in his comment that VisualTreeHelper.GetChildrenCount() is returning 0. This happens because p is not part of VisualTree, once it is part of VisualTree it will work correctly.
Page p;
private void Button_Click_1(object sender, RoutedEventArgs e)
{
p = (Page) Application.LoadComponent(new Uri("Views/EmployeeDetaiView.Xaml.xaml", UriKind.Relative));
Frm.Content = p;
// This will print 0
int i = VisualTreeHelper.GetChildrenCount(p);
System.Diagnostics.Debug.WriteLine("Children count = " + i);
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
// Now it will correctly print 1, as 'p' is now part of VisualTree
int i = VisualTreeHelper.GetChildrenCount(p);
System.Diagnostics.Debug.WriteLine("Children count = " + i);
}
Related
I made a custom style for a slider to which I only change the shape of the Thumb.
What I would like to do is have a function which changes the size of the Thumb whenever it is triggered (maybe via a button).
The way I created my custom style is: right-click on Slider -> Edit Template -> Edit a copy
My problem is that I don't know how can I access the thumb of the slider...
I would like something like this
Thumb myThumb = mySlider.GetTemplateChild("horizontalThumb");
myThumb.Height = 50;
I saw multiple ways to do that in WPF but not in UWP.
To access the Thumb from your Slider, try this:
1: Add the Loaded event in the XAML to your slider
2: Use this function to get the child from the parent
//Get the acutal element from the parent object using the VisualTreeHelper:
//Parameters:
//parent = The object to get the element from
//childname = The name of the childobject to find
private DependencyObject GetElementFromParent(DependencyObject parent, string childname)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i =0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is FrameworkElement childframeworkelement && childframeworkelement.Name == childname)
return child;
var FindRes = GetElementFromParent(child, childname);
if (FindRes != null)
return FindRes;
}
return null;
}
3: Put this code in your slider_loaded event to get the data from the Thumb:
private void Slider_Loaded(object sender, RoutedEventArgs e)
{
var SliderThumb = GetElementFromParent(sender as DependencyObject, "HorizontalThumb"); //Make sure to put the right name for your slider layout options are: ("VerticalThumb", "HorizontalThumb")
if (SliderThumb != null)
{
if(SliderThumb is Thumb thumb)
{
//Here you can change everything you like:
thumb.Background = new SolidColorBrush(Colors.Blue);
thumb.CornerRadius = new CornerRadius(5);
thumb.Width = 10;
thumb.Height = 10;
}
else
{
//SliderThumb is not an object of type Thumb
}
}
else
{
//SliderThumb is null
}
}
I am trying to create a UWP control that is a combobox but whose width stays constant no matter which item is selected. (The default ComboBox will resize itself every time a new item is picked, which may cause the layout of the rest of the page to change.)
I am basing my control off of one that I wrote for WPF, which works fine. This is the code for the UWP version:
using Windows.UI.Xaml.Controls;
using Windows.Foundation;
using System.Reflection;
namespace Foo
{
public sealed class ConstantWidthComboBox : ComboBox
{
private readonly double _emptyWidth;
public ConstantWidthComboBox()
{
this.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_emptyWidth = this.DesiredSize.Width;
}
protected override void OnItemsChanged(object e)
{
base.OnItemsChanged(e);
AdjustWidth();
}
private void AdjustWidth()
{
double maxItemWidth = 0;
foreach (var item in Items)
{
if (item is ComboBoxItem cmbItem)
{
cmbItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (cmbItem.DesiredSize.Width > maxItemWidth)
maxItemWidth = cmbItem.DesiredSize.Width;
}
else
{
object content = item;
if (!string.IsNullOrEmpty(this.DisplayMemberPath))
{
content = EvaluateDisplayMemberPath(item, this.DisplayMemberPath);
}
ContentPresenter presenter = new ContentPresenter() { Content = content };
presenter.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (presenter.DesiredSize.Width > maxItemWidth)
maxItemWidth = presenter.DesiredSize.Width;
}
}
this.Width = maxItemWidth + _emptyWidth + this.Padding.Left + this.Padding.Right;
}
private object EvaluateDisplayMemberPath(object item, string path)
{
if (item == null)
return null;
if (string.IsNullOrEmpty(path))
return item;
int k = path.IndexOf(".");
string property = (k == -1) ? path : path.Substring(0, k);
string trailer = (k == -1) ? null : path.Substring(k + 1);
object value = item.GetType().GetProperty(property, BindingFlags.Public | BindingFlags.Instance).GetValue(item, null);
return EvaluateDisplayMemberPath(value, trailer);
}
}
}
(Note that this works if the combobox consists of ComboBoxItems or if the DisplayMemberPath is used to get the text to display. I haven't tested it on wider circumstances such as if the items are data templates or whatnot.)
While this works in WPF, in UWP the presenter's DesiredSize.Width value is always zero.
Does anyone know how to measure the (hypothetical) width of a control that is not actually part of the UI layout in UWP, such as for the ContentPresenter in the above code?
Please find the ContentPresenter control through Visual Tree, then access the ActualWidth property.
You can follow the first answer of this question edited by Martin Zikmund to do this like following.
private void Page_Loaded(object sender, RoutedEventArgs e)
{
var contentPresenter = FindChild<ContentPresenter>(MyConstantWidthComboBox);
double k = contentPresenter.ActualWidth;
}
public T FindChild<T>(DependencyObject parent)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T typedChild)
{
return typedChild;
}
var inner = FindChild<T>(child);
if (inner != null)
{
return inner;
}
}
return default;
}
i'm starting with WPF and i have a problem. Please help me. Thanks, sorry for my bad English!
I have added ContentControl to a Canvas, and i want to remove/delete it.
Draw ContentControl code:
ContentControl cc = new ContentControl();
cc.Content = shape;
cc.Height = h;
cc.Width = w;
Style s = myCanvas.FindResource("DesignerItemStyle") as Style;
cc.Style = s;
Canvas.SetLeft(cc, x);
Canvas.SetTop(cc, y);
myCanvas.Children.Add(cc);
I use HitTest to remove it but i can remove only shape
private void myCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((Canvas)sender);
HitTestResult result = VisualTreeHelper.HitTest(myCanvas, pt);
if (result != null)
{
myCanvas.Children.Remove(result.VisualHit as Shape); //it works with shape
// i have changed it into myCanvas.Children.Remove(result.VisualHit as ContentControl);
//but it didn't work with ContentControl
}
}
It is so because the ContentControl is the parent of the Shape, and the Canvas's children contains the ContentControl that hosts the shape.
You could do this to fix your issue :)
private void myCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
DependencyObject ob = FindAncestor<ContentControl>((DependencyObject)e.Source);
if (ob.GetType() == typeof(ContentControl))
myCanvas.Children.Remove(ob as ContentControl);
}
public T FindAncestor<T>(DependencyObject dependencyObject)
where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindAncestor<T>(parent);
}
I suggest you the next solution:
private void myCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var dObj = sender as DependencyObject;
if(dObj == null) return;
var ob = dObj.GetChildOfType<ContentControl>();
if (ob != null)
{
myCanvas.Children.Remove(ob);
}
}
Helper code:
public static T GetChildOfType<T>(this DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
just put the helper inside the public static class as it is an extension method
regards
You can add the control, for example:
Rectangle r = new Rectangle() { Name = "MyName" };
and to remove from the canvas:
UIElement element = StationLayout.FindName ("MyName") as UIElement;
StationLayout.Children.Remove(element);
I'm using VisualTreeHelper.GetChildrenCount() to find child controls, but it always return 0.
Here is my code
<ScrollViewer x:Name="scrollViewerChannelsRecordTimeData">
<StackPanel x:Name="channelsRecordTimeData">
<ItemsControl x:Name="channelRecordTimeItems" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="hoursLines">
//Some Controls here
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
C# code:
channelRecordTimeItems.ItemContainerGenerator.StatusChanged += ChannelRecordTimeItemsStatusChangedEventHandler;
private void ChannelRecordTimeItemsStatusChangedEventHandler(Object sender, EventArgs e)
{
if (channelRecordTimeItems.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
if (channelRecordTimeItems.HasItems)
{
DependencyObject dependencyObject = null;
Grid gridHighlightRecordData = null;
for (int i = 0; i < channelRecordTimeItems.Items.Count; i++)
{
dependencyObject = channelRecordTimeItems.ItemContainerGenerator.ContainerFromIndex(i); //dependencyObject != null
if (dependencyObject != null)
{
Grid hoursLines = FindElement.FindChild<Grid>(dependencyObject, "hoursLines"); //hoursLines = null
}
}
}
}
}
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent); //Return 0 here
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
VisualTreeHelper.GetChildrenCount() always return 0,
The code for constructing for items here
List<ChannelRecordTimeItemData> listChannelRecordTimeItemData = new List<ChannelRecordTimeItemData>();
for(int i = 0; i < 5; i++)
{
ChannelRecordTimeItemData item = new ChannelRecordTimeItemData();
listChannelRecordTimeItemData.Add(ChannelRecordTimeItemData);
}
channelRecordTimeItems.ItemsSource = listChannelRecordTimeItemData;
channelRecordTimeItems.Items.Refresh();
I have searched on forum and internet, but i can not solve it, someone can help me?
Many thanks!
T&T
The problem is that when the ItemContainerGenerator signals the ContainersGenerated status, the container (a ContentPresenter) has been created, but not yet loaded. Especially the data template has not yet been applied to the ContentPresenter, hence there is nothing in the visual tree.
You may get around this by adding a Loaded event handler when looping over the generated containers.
private void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
{
if (itemsControl.ItemContainerGenerator.Status
== GeneratorStatus.ContainersGenerated)
{
var containers = itemsControl.Items.Cast<object>().Select(
item => (FrameworkElement)itemsControl
.ItemContainerGenerator.ContainerFromItem(item));
foreach (var container in containers)
{
container.Loaded += ItemContainerLoaded;
}
}
}
private void ItemContainerLoaded(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
element.Loaded -= ItemContainerLoaded;
var grid = VisualTreeHelper.GetChild(element, 0) as Grid;
...
}
If your using Caliburn.Micro this will help you.
For your Viewmodel the base Class should be Screen then only VisualTreeHelper.GetChildrenCount() give no.of childs.(because Screen will Activate all childs
or
otherwise (FrameworkElement)YourParent).ApplyTemplate() method
I need to have control whose position is directly correlated by the scroll offset of a ScrollViewer in Windows Phone 8 SDK (silverlight/wpf). Additionally I need to be able to tell what the scroll offset in a delegate of sorts so that I may change other in-app properties. Is this even possible?
I have looked all over but can not seem to find any example, nor do I seem to have a grasp of WPF/Silverlight's animation concepts enough to pick this up.
The best that I could come up with is shown below. It would appear at first to work, but unfortunately will only update when your finger is not down and the ScrollViewer is not animating, so the updates come too infrequently. I need the updates to come as a part of the animation loop, so every frame or so (60-100+ per second), I get the new scroll offset value. Is there any way to schedule the DispatchTimer in the animation loop? Or would there be some sort of better way to approach this entirely, using something like DependentProperties?
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
DispatcherTimer t = new DispatcherTimer();
t.Interval = TimeSpan.FromMilliseconds(16.6);
t.Tick += new EventHandler(
(object s, EventArgs ee) =>
{
// FunkBox is some ListBox
ScrollViewer sv = FindChildOfType<ScrollViewer>(FunkBox);
if (sv == null)
{
// TOffset is some TextBlock
TOffset.Text = "dur...";
}
else
{
TOffset.Text = String.Format("dur {0}", sv.HorizontalOffset);
}
});
t.Start();
}
static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = System.Windows.Media.VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
{
var child = System.Windows.Media.VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
This took me a while to figure out too. Here's how you do it:
Ensure that your ScrollViewer has ManipulationMode set to 'Control'
Walk through the visual tree to find the Vertical Scrollbar child of the ScrollViewer.
Hook into its ValueChanged event.
So, you XAML would be:
<ScrollViewer x:Name="mainScrollViewer" ManipulationMode="Control">
....
</ScrollViewer>
And your code behind:
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ScrollBar verticalBar;
verticalBar = ((FrameworkElement)VisualTreeHelper.GetChild(mainScrollViewer, 0)).FindName("VerticalScrollBar") as ScrollBar;
verticalBar.ValueChanged += verticalBar_ValueChanged;
}
void verticalBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
double newVerticalOffset = e.NewValue;
// Set the offset of your other control here, using newVerticalOffset
}
or, for a ListBox, you would need to get the ScrollViewer from inside the Listbox using code such as:
ScrollViewer mainScrollViewer = GetVisualChild<ScrollViewer>(yourListBoxControl);
// ...then use the code above
public T GetVisualChild<T>(UIElement parent) where T : UIElement
{
T child = null; // default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
UIElement element = (UIElement)VisualTreeHelper.GetChild(parent, i);
child = element as T;
if (child == null)
child = GetVisualChild<T>(element);
if (child != null)
break;
}
return child;
}
You might also need to set the manipulation mode of the ScrollViewer in your constructor:
ScrollViewer mainScrollViewer = GetVisualChild<ScrollViewer>(lstTest);
sv.ManipulationMode = ManipulationMode.Control;