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
}
}
Related
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 am creating a WPF application for reading logs. Currently, I have Expanders inside of Expanders. When I click on an Expander, I want the scope to be the contents of that Expander. All other peer Expanders disappear (Visibility.Collapsed).
This works for the first line of Expanders. Once I expand a child Expander, this works again, but the parent Expander is still listed above it.
I'd like to collapse peers (same as before) as well as the parent, but keep the parent expanded, so that just the expanded child is showing by itself.
That was a lot of "expand" in a few sentences! But anyways ...
Here's an example of what expected output:
Initially:
Expander 1
Expander 2
Expander 3
Expander 4
Let's say I click Expander 2:
Expander 2
Child Expander 1
Child Expander 2
Child Expander 3
Child Expander 4
All others are collapsed, but still expanded. Children are displayed.
Let's say I click Child Expander 3
Child Expander 3
All others are collapsed, including the parent!
Is there a way to accomplish this?
Here's my current code (Toggling is kindof confusing at first):
private Boolean toggleLogs = false;
private Boolean subToggleLogs = false;
public LogsControl()
{
InitializeComponent();
}
private void expanderPanel_Click(object sender, RoutedEventArgs e)
{
toggleLogs = !toggleLogs;
if (toggleLogs == true)
{
setLogVisibility(expanderPanel);
} else
{
toggleLogVisibility(expanderPanel);
}
}
private void windowsServicePanel_Click(object sender, RoutedEventArgs e)
{
toggleLogs = false;
subToggleLogs = !subToggleLogs;
if (subToggleLogs == true)
{
setLogVisibility(windowsServicePanel);
}
else
{
toggleLogVisibility(windowsServicePanel);
}
}
private void javaPanel_Click(object sender, RoutedEventArgs e)
{
toggleLogs = false;
subToggleLogs = !subToggleLogs;
if (subToggleLogs == true)
{
setLogVisibility(javaPanel);
}
else
{
toggleLogVisibility(javaPanel);
}
}
public void toggleLogVisibility(StackPanel panel)
{
var childNumber = VisualTreeHelper.GetChildrenCount(panel);
for (var i = 0; i <= childNumber - 1; i++)
{
var uiElement = VisualTreeHelper.GetChild(panel, i) as Expander;
uiElement.Foreground = new SolidColorBrush(Colors.White);
if (uiElement.IsExpanded == false)
{
uiElement.Visibility = Visibility.Visible;
}
}
}
public void setLogVisibility(StackPanel panel)
{
var childNumber = VisualTreeHelper.GetChildrenCount(panel);
for (var i = 0; i <= childNumber - 1; i++)
{
var uiElement = VisualTreeHelper.GetChild(panel, i) as Expander;
if (uiElement.IsExpanded == true)
{
uiElement.Foreground = (SolidColorBrush)(new BrushConverter().ConvertFrom("#FF3399FF"));
uiElement.Visibility = Visibility.Visible;
}
else
{
uiElement.Visibility = Visibility.Collapsed;
}
}
}
If you set a controls visibility to collapsed or hidden, all child controls and content is collapsed / hidden. No way around that. You'd have to simulate the hierarchy if you want to do what you want. Instead of actually nesting the expanders, just make it look like it on the screen, but you'll have difficulty keeping all the visibility states in sync that way.
I've listbox which has some collection binded & once the content size gets increased listbox gets vertical scrollviewer(default) visible. I got scrollbar from this scrollviewer as child element. But, when I'm trying to get child element (e.g. repeatbutton, thumb) from scrollbar then i get child element null. I got control hierarchy from silverlight spy. I want to get repeatbutton from vertical scrollabr(This is not custom scrollbar). For e.g.
Code :
var objVRepeatBtn = ((FrameworkElement)VisualTreeHelper.GetChild(objvScrollBar, 0)).FindName("VerticalSmallIncrease") as System.Windows.Controls.Primitives.RepeatButton;
Any approach will be accepted.
I resolved this with approach like :
private RepeatButton rb = null;
private RepeatButton rb1 = null;
private Thumb thumb = null;
public ThumbnailUserControl()
{
InitializeComponent();
//sv1 repensented as ScrollViewer's object
this.sv1.Loaded += new RoutedEventHandler(sv1_Loaded);
}
void sv1_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement fe = VisualTreeHelper.GetChild(this.sv1, 0) as FrameworkElement;
if (fe == null)
return;
var sb = fe.FindName("VerticalScrollBar") as ScrollBar;
if (sb != null)
{
thumb = (Thumb)((FrameworkElement)VisualTreeHelper.GetChild(sb, 0)).FindName("VerticalThumb");
rb1 = (RepeatButton)((FrameworkElement)VisualTreeHelper.GetChild(sb, 0)).FindName("VerticalLargeDecrease");
rb = (RepeatButton)((FrameworkElement)VisualTreeHelper.GetChild(sb, 0)).FindName("VerticalSmallIncrease");
rb.Click += new RoutedEventHandler(rb_Click);
thumb.DragCompleted += new DragCompletedEventHandler(thumb_DragCompleted);
thumb.MouseWheel += new MouseWheelEventHandler(thumb_MouseWheel);
}
}
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;
I use RadioButton to Create Custom Control and want to know How do I detect it when mouse move over it while its left button is pressed and held down? Of course I know it is possible with VisualTreeHelper but this method returns only top most element (not my own custom control).
You can use a snippet like this to dig deeper in the VisualTree, and return the first control of the specified type it finds:
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
To find a MyCustomControl inside the someVisual control:
MyCustomControl myControl = GetVisualChild<MyCustomControl>(someVisual);