Animate via Horizontal/Vertical offset of ScrollViewer - c#

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;

Related

Get Thumb of a Slider in UWP

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
}
}

Collapsing parent Expander visibility in WPF without collapsing child Expander

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.

TabPage title alignment being wrong after drag'n'dropping

I have the class that extends System.Windows.Forms.TabControl and had implemented drag'n'drop mechanism for its TabPages as following:
#region Overriden base methods
protected override void OnDragOver(DragEventArgs e)
{
if (PointedTabPage == null) return;
e.Effect = DragDropEffects.Move;
var dragTab = e.Data.GetData(typeof (ManagedTabPage)) as ManagedTabPage;
if (dragTab == null) return;
int dropIndex = TabPages.IndexOf(PointedTabPage);
int dragIndex = TabPages.IndexOf(dragTab);
if (dragIndex == dropIndex) return;
var modifiedTabPages = new List<ManagedTabPage>(from ManagedTabPage tabPage in TabPages
where TabPages.IndexOf(tabPage) != dragIndex
select TabPages[TabPages.IndexOf(tabPage)] as ManagedTabPage);
modifiedTabPages.Insert(dropIndex, dragTab);
for (byte i = 0; i < TabPages.Count; ++i)
{
var managedTabPage = TabPages[i] as ManagedTabPage;
if (managedTabPage != null && managedTabPage.Uid == modifiedTabPages[i].Uid)
continue;
TabPages[i] = modifiedTabPages[i];
}
SelectedTab = dragTab;
}
protected override void OnMouseDown(MouseEventArgs e)
{
try
{
switch (e.Button)
{
case MouseButtons.Left:
DoDragDrop(PointedTabPage, DragDropEffects.Move);
break;
case MouseButtons.Middle:
CloseTab(PointedTabPage);
break;
}
}
catch (InvalidOperationException)
{
}
finally
{
TabPages.Insert(0, String.Empty);
TabPages.RemoveAt(0);
}
}
#endregion
Nota bene that in the finally clause of OnMouseDown method there are 2 lines for workarounding the
problem: for some reason w/o these lines after drag'n'dropping any of TabPages alignment of their titles is being wrong:
What should I do to correct this behavior without this smelly workaround? Maybe sending some Windows messages could do the trick?
Thanks a lot for any suggestions.
Edit 1. Code of ManagedTabPage is 100% unimportant (it's just extends TabPage with some specific properties).
PointedTabPage is unimportant too, but this is it:
return (from ManagedTabPage tabPage in TabPages
let tabPageIndex = TabPages.IndexOf(tabPage)
where GetTabRect(tabPageIndex).Contains(PointToClient(Cursor.Position))
select TabPages[tabPageIndex]).Single() as ManagedTabPage;
I'm trying to achieve fully-centered alignment of labels. You see, labels on the screenshot didn't centered horizontally?
I can't do much with the posted code. Let's take a completely different tack and create a tabcontrol that supports dragging a tabpage with the mouse. Add a new class to your project and paste this code:
using System;
using System.Drawing;
using System.Windows.Forms;
class TabControlEx : TabControl {
private Point downPos;
private Form draggingHost;
private Rectangle draggingBounds;
private Point draggingPos;
public TabControlEx() {
this.SetStyle(ControlStyles.UserMouse, true);
}
}
The usage of the variable becomes clear later. First thing we need is to get mouse events from the TabControl so we can see the user trying to drag a tab. That requires turning on the UserMouse control style, it is off by default for controls (like TabControl) that are built-in Windows controls and use their own mouse handling.
Use Build > Build and drag the new control from the top of the toolbox onto a form. Everything still looks and acts like a regular TabControl, but do note that clicking tabs no longer changes the active tab. A side-effect of turning the UserMouse style on. Let's fix that first, paste:
protected override void OnMouseDown(MouseEventArgs e) {
for (int ix = 0; ix < this.TabCount; ++ix) {
if (this.GetTabRect(ix).Contains(e.Location)) {
this.SelectedIndex = ix;
break;
}
}
downPos = e.Location;
base.OnMouseDown(e);
}
We are storing the click location, that's needed later to detect the user dragging the tab. That requires the MouseMove event, we need to start dragging when the user moved the mouse far enough away from the original click position:
protected override void OnMouseMove(MouseEventArgs e) {
if (e.Button == MouseButtons.Left && this.TabCount > 1) {
var delta = SystemInformation.DoubleClickSize;
if (Math.Abs(e.X - downPos.X) >= delta.Width ||
Math.Abs(e.Y - downPos.Y) >= delta.Height) {
startDragging();
}
}
base.OnMouseMove(e);
}
The startDragging method needs to create a toplevel window that can be moved around with the mouse, containing a facsimile of the tab we're dragging around. We'll display it as an owned window, so it is always on top, that has the exact same size as the tab control:
private void startDragging() {
draggingBounds = this.RectangleToScreen(new Rectangle(Point.Empty, this.Size));
draggingHost = createDraggingHost(draggingBounds);
draggingPos = Cursor.Position;
draggingHost.Show(this.FindForm());
}
The createDraggingHost needs to do the heavy lifting and create a window that looks just like the tab. A borderless form we'll move around with the mouse. We'll use the TransparencyKey property to make it look similar to the dragged TabPage with a tab sticking out at the top. And make it look the same by simply letting it display a screenshot of the tabpage:
private Form createDraggingHost(Rectangle bounds) {
var host = new Form() { FormBorderStyle = FormBorderStyle.None, ControlBox = false, AutoScaleMode = AutoScaleMode.None, Bounds = this.draggingBounds, StartPosition = FormStartPosition.Manual };
host.BackColor = host.TransparencyKey = Color.Fuchsia;
var tabRect = this.GetTabRect(this.SelectedIndex);
var tabImage = new Bitmap(bounds.Width, bounds.Height);
using (var gr = Graphics.FromImage(tabImage)) {
gr.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
gr.FillRectangle(Brushes.Fuchsia, new Rectangle(0, 0, tabRect.Left, tabRect.Height));
gr.FillRectangle(Brushes.Fuchsia, new Rectangle(tabRect.Right, 0, bounds.Width - tabRect.Right, tabRect.Height));
}
host.Capture = true;
host.MouseCaptureChanged += host_MouseCaptureChanged;
host.MouseUp += host_MouseCaptureChanged;
host.MouseMove += host_MouseMove;
host.Paint += (s, pe) => pe.Graphics.DrawImage(tabImage, 0, 0);
host.Disposed += delegate { tabImage.Dispose(); };
return host;
}
Note the use of the Capture property, that's how we detect that the user released the mouse button or interrupted the operation by any other means. We'll use the MouseMove event to move the window around:
private void host_MouseMove(object sender, MouseEventArgs e) {
draggingHost.Location = new Point(draggingBounds.Left + Cursor.Position.X - draggingPos.X,
draggingBounds.Top + Cursor.Position.Y - draggingPos.Y);
}
And finally we need to handle the completion of the drag. We'll swap tabs, inserting the dragged tab at the mouse position and destroy the window:
private void host_MouseCaptureChanged(object sender, EventArgs e) {
if (draggingHost.Capture) return;
var pos = this.PointToClient(Cursor.Position);
for (int ix = 0; ix < this.TabCount; ++ix) {
if (this.GetTabRect(ix).Contains(pos)) {
if (ix != this.SelectedIndex) {
var page = this.SelectedTab;
this.TabPages.RemoveAt(this.SelectedIndex);
this.TabPages.Insert(ix, page);
this.SelectedIndex = ix;
}
break;
}
}
draggingHost.Dispose();
draggingHost = null;
}
Looks pretty good.
since you hasn't shared ManagedTabPage code, i used default TabPage control
changes are made in method OnDragOver
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace Demo
{
public class MyTabControl : TabControl
{
public MyTabControl()
{
SizeMode = TabSizeMode.Fixed;
ItemSize = new Size(224, 20);
}
#region Overriden base methods
protected override void OnDragOver(DragEventArgs e)
{
if (DesignMode)
return;
if (PointedTabPage == null) return;
e.Effect = DragDropEffects.Move;
var dragTab = e.Data.GetData(typeof(TabPage)) as TabPage;
if (dragTab == null) return;
int dropIndex = TabPages.IndexOf(PointedTabPage);
int dragIndex = TabPages.IndexOf(dragTab);
if (dragIndex == dropIndex) return;
// change position of tab
TabPages.Remove(dragTab);
TabPages.Insert(dropIndex, dragTab);
SelectedTab = dragTab;
base.OnDragOver(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (DesignMode)
return;
switch (e.Button)
{
case MouseButtons.Left:
DoDragDrop(PointedTabPage, DragDropEffects.Move);
break;
case MouseButtons.Middle:
TabPages.Remove(PointedTabPage);
break;
}
}
#endregion
TabPage PointedTabPage
{
get
{
return TabPages.OfType<TabPage>()
.Where((p, tabPageIndex) => GetTabRect(tabPageIndex).Contains(PointToClient(Cursor.Position)))
.FirstOrDefault();
}
}
}
}

Add ListBox to DataTemplate from C# code

I am trying to add HubSections dynamically (C# code) - that works.
Then from the same code I want to add ListBoxes to each of 'em - and apparently I have no idea how to do that.
I found several examples like adding:
ContentTemplate = new DataTemplate() { VisualTree = ... }
... to HubSection constructor but there is no VisualTree in DataTemplate.
Please, ask for any details if my problem description is too vague - I am a WP8.1 newbie so I could skip some important info.
The best way I found is to create DataTemplate like this:
public class ViewSection : HubSection {
public ViewSection(View view) {
string xaml = "<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'><StackPanel /></DataTemplate>";
ContentTemplate = XamlReader.Load(xaml) as DataTemplate;
this.Loaded += ViewSection_Loaded;
}
private void ViewSection_Loaded(object sender, RoutedEventArgs e) {
base.OnApplyTemplate();
StackPanel stackPanel = findStackPanelInSubtree(this);
...
< adding stuff to stack panel >
...
this.Loaded -= ViewSection_Loaded;
}
private StackPanel findStackPanelInSubtree(FrameworkElement element) {
if (element != null) {
if (element.GetType() == typeof(StackPanel)) {
return element as StackPanel;
}
foreach (FrameworkElement child in getChildren(element)*) {
StackPanel result = findStackPanelInSubtree(child);
if (result != null) return result;
}
}
return null;
}
private List<FrameworkElement> getChildren(FrameworkElement element)* {
if (element != null) {
List<FrameworkElement> result = new List<FrameworkElement>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) {
result.Add(VisualTreeHelper.GetChild(element, i) as FrameworkElement);
}
return result;
}
return null;
}
}
Is this some kind of cruel joke of Microsoft devs or there is a better way to do this?
* of course this is totally redundant but foreach makes it sooo much nicer to read
Unavailability of the whole children collection looks not really convenient. You may however rewrite your handy getChildren method to exhibit yield keyword usage:
private List<FrameworkElement> getChildren(FrameworkElement element) {
if (element != null) {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) {
yield return (VisualTreeHelper.GetChild(element, i) as FrameworkElement);
}
}
yield break;
}

How to get child element of default scrollbar(e.g. repeatbutton, thumb, etc.) in silverlight?

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);
}
}

Categories