Freezing part of a canvas when scrolling in WPF? - wpf

I'm a bit of a novice with WPF and XAML. I'm working on a piano roll interface for reading MIDI files, which, so far, looks something like this:
https://i.imgur.com/hBJZhnH.png
I have a canvas for the whole piano roll (wrapped around a ScrollViewer for horizontal and vertical scrolling). In that canvas, I have a grdPiano grid for drawing the piano notes, a grdGridColours grid for drawing the horizontal row fill colours, a cnvGridLines canvas where I draw the vertical grid lines (this is done in C# code), and a grdNotes grid where I draw the MIDI notes in C# code.
This works fine, but when I scroll horizontally, the piano keys (grdPiano) scroll out of view. I want it such that if I scroll vertically, everything scrolls, but if I scroll horizontally, the piano keys stay frozen in view on the left side of the screen. I've been messing around with nested ScrollViewers but I can't seem to get it to work.
Here's my XAML:
<ScrollViewer x:Name="srlPianoScroll" Margin="125,20,0,0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="cnvPianoRoll" HorizontalAlignment="Left" Height="1592" VerticalAlignment="Center" Width="132">
<Grid x:Name="grdPiano" HorizontalAlignment="Left" Height="1638" VerticalAlignment="Center" Width="132" Canvas.Top="-8">
<Grid.RowDefinitions>
<!--Row definitions...-->
</Grid.RowDefinitions>
<!--Drawing piano notes...-->
</Grid>
<Grid x:Name="grdGridColours" HorizontalAlignment="Left" Height="1628" VerticalAlignment="Center" Width = "0" Canvas.Top="2" Canvas.Left="132" Panel.ZIndex="1">
<Grid.RowDefinitions>
<!--Row definitions...-->
</Grid.RowDefinitions>
<!--Drawing horizontal fill colours...-->
</Grid>
<Canvas x:Name="cnvGridLines" HorizontalAlignment="Left" Height="1592" VerticalAlignment="Center" Width = "0" Canvas.Top="2" Canvas.Left="132" Panel.ZIndex="2"/>
<!--Vertical grid lines are drawn programatically.-->
<Grid x:Name="grdNotes" HorizontalAlignment="Left" Height="1628" VerticalAlignment="Center" Width = "0" Canvas.Top="2" Canvas.Left="132" Panel.ZIndex="3">
<Grid.RowDefinitions>
<!--Row definitions...-->
</Grid.RowDefinitions>
</Grid>
</Canvas>
</ScrollViewer>
Thanks!

Try binding grdPiano's Canvas.Left to the scrollviewer's HorizontalOffset.
When the scrollviewer scrolls horizontally, the binding will update Canvas.Left to be on the left edge of the visible area. You can use a converter to add an offset, if needed.
Here is an example:
XAML
<Window x:Class="WpfApp38.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp38"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:OffsetConverter x:Key="OffsetConverter" />
</Window.Resources>
<ScrollViewer Name="sv" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas Width="10600" Height="500">
<!-- Rectangle that stays on the left side, your grdPiano -->
<Rectangle Canvas.Left="{Binding ElementName=sv, Path=HorizontalOffset}" Canvas.Top="0" Width="50" Height="500" Fill="Blue" Panel.ZIndex="1" />
<!-- Rectangle that stays on the left side, your grdPiano - - using the OffsetConverter to offset it 300 to the right -->
<!--<Rectangle Canvas.Left="{Binding ElementName=sv, Path=HorizontalOffset, Converter={StaticResource OffsetConverter}, ConverterParameter=300}" Canvas.Top="0" Width="50" Height="500" Fill="LightBlue" Panel.ZIndex="1" />-->
<!-- Rectangles that scrolls normally -->
<Rectangle Canvas.Left="150" Canvas.Top="50" Width="200" Height="25" Fill="Black" />
<Rectangle Canvas.Left="50" Canvas.Top="100" Width="100" Height="25" Fill="Black" />
<Rectangle Canvas.Left="350" Canvas.Top="200" Width="250" Height="25" Fill="Black" />
<Rectangle Canvas.Left="600" Canvas.Top="235" Width="10000" Height="25" Fill="Black" />
</Canvas>
</ScrollViewer>
</Window>
And the code for the converter:
public class OffsetConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double scrollViewerHorizontalOffset && parameter is string s)
{
double offsetAmount;
double.TryParse(s, out offsetAmount);
return scrollViewerHorizontalOffset + offsetAmount;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Related

Capturing name of StackPanel from Drop event

Within WPF I have the following XAML code:
<Page x:Class="com.MyCo.MyProj.Pages.Configuration.ManageLinkage.MasterLinkage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:com.MyCo.MyProj.Pages.Configuration.ManageLinkage"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="MasterLinkage">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TabControl TabStripPlacement="Top" Background="Transparent">
<TabItem Header="Import">
<ListBox Margin="0,5,0,0" Name="lbxImportItems" HorizontalAlignment="Left" VerticalAlignment="Top" Width="110" Background="Transparent"
PreviewMouseLeftButtonDown="lbxImportItems_PreviewMouseLeftButtonDown" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Name="DBImport">
<Image Source="/Images/DBImport25px.png" VerticalAlignment="Center" HorizontalAlignment="Center"></Image>
<TextBlock Text="Database" Foreground="AntiqueWhite"/>
</StackPanel>
<StackPanel Orientation="Vertical" Name="CSVImport">
<Image Source="/Images/CSVImport25px.png" VerticalAlignment="Center" HorizontalAlignment="Center"></Image>
<TextBlock Text="CSV Import" Foreground="AntiqueWhite"/>
</StackPanel>
</ListBox>
</TabItem>
</TabControl>
<Canvas x:Name="cnvsLinkScreen" AllowDrop="True" Grid.Column="1" Background="Transparent" Drop="cnvsLinkScreen_Drop" DragOver="cnvsLinkScreen_DragOver" ></Canvas>
</Grid>
The code for capturing the event is here:
private void cnvsLinkScreen_Drop(object sender, DragEventArgs e)
{
Canvas parent = (Canvas)sender;
object data = e.Data.GetData(typeof(string));
StackPanel objIn = (StackPanel)e.Data;
...
}
The drag and drop work great, the event method created the image in the canvas. However, I want to capture the Name="" from the StackPanels which are dropped.
I found the Name buried super deep in the "DragEventArgs e" object. I was think that there should be a way to cast the object (or the object within that object) as a StackPanel to easily work with it. The above code does not convert the StackPanel object( it's not at the root or the child object; I tried both) so it exceptions on "StackPanel objIn = (StackPanel)e.data;"
How do I either translate the incoming object to a StackPanel or how do I access the Name attribute from the Stackpanel?
I got it. I was close with the translation. To translate / typecast the object to what you are working with I needed to use the following line:
StackPanel objIn = (StackPanel)(e.Data.GetData(typeof(StackPanel)));
Which is slightly different than above.

Vlc.DotNet WPF video background issue

There is my code. I see the video in the top right corner, where control itself is located, but the main grid background is empty. It's supposed to take video through VisualBrush, right? I've googled few samples and they all use the same trick, but it doesn't work...
I've also tried to put some controls on top of the control, but nothing shows through, because I assume it's using WinForms control inside, which is top-most.
So how do I get this video as the background?
<Grid>
<vlc:VlcControl x:Name="myVlcControl" Width="100" Height="100" HorizontalAlignment="Right" VerticalAlignment="Top" />
<Grid>
<Grid.Background>
<VisualBrush Stretch="Uniform">
<VisualBrush.Visual>
<Image Source="{Binding VideoSource, ElementName=myVlcControl}" />
</VisualBrush.Visual>
</VisualBrush >
</Grid.Background>
</Grid>
MediaElement supports RTSP just fine, but it may not support the encoding/container you're trying to work with. The following produces a working streaming MediaElement, and uses a VisualBrush to paint the background of a Grid with the MediaElement:
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<MediaElement x:Name="MyPlayer"
Width="640"
Height="480"
LoadedBehavior="Play"
Source="rtsp://granton.ucs.ed.ac.uk/domsdemo/v2003-1.wmv" />
<Grid Grid.Row="1"
Width="320"
Height="240">
<Grid.Background>
<VisualBrush Stretch="Uniform" Visual="{Binding ElementName=MyPlayer}" />
</Grid.Background>
</Grid>
</Grid>
#Kolorowezworki made Airhack control to workaround this issue.
Example:
<airhack:AirControl DataContext="{Binding}">
<airhack:AirControl.Front>
<Image Source="{Binding VideoSource, ElementName=myVlcControl}" />
</airhack:AirControl.Front>
<airhack:AirControl.Back>
<vlc:VlcControl x:Name="myVlcControl" Width="100" Height="100" HorizontalAlignment="Right" VerticalAlignment="Top" />
</airhack:AirControl.Back>
</airhack:AirControl>
NOTE: By default AirControl does't support DataContext Binding, to solve this issue fork or copy repository and implement DataContext support by passing it 'airhack' window.
Example:
public AirControl()
{
InitializeComponent();
alpha = new Alpha(this);
alpha.DataContext = DataContext;
DataContextChanged += (sender, args) => alpha.DataContext = DataContext;
}

How to make all controls resize accordingly proportionally when window is maximized?

When I clicked on the maximize button the window is maximized but the controls are not resized proportionally. What is the best way to make the controls resize accordingly? I am using MVVM.
Here is my code.
<Window x:Class="DataTransfer.View.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Icon="/DataTransfer;component/View/Images/ms_msnexplore.gif"
ResizeMode="CanResizeWithGrip"
Title="Window1" Height="500" Width="600">
<!--Style="{DynamicResource OfficeStyle}"-->
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--<ResourceDictionary Source="/DataTransfer;component/View/WindowBase.xaml" />-->
<!--<ResourceDictionary Source="/DataTransfer;component/Themes/WPFThemes/CalendarResource.xaml" />-->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width ="*" />
</Grid.ColumnDefinitions>
<Button Content="Button" HorizontalAlignment="Left" Margin="52,28,0,0" VerticalAlignment="Top" Width="75" Height="22" />
<DatePicker Name="dp" HorizontalAlignment="Left" Margin="175,25,0,0" VerticalAlignment="Top" Width="123" Text="aaa" GotFocus="DateGotFocused" LostFocus="OnLeaveArchiveDate"/>
<Calendar HorizontalAlignment="Left" Margin="47,162,0,0" VerticalAlignment="Top"/>
<TextBox Name="t1" HorizontalAlignment="Left" Height="23" Margin="337,23,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120" LostFocus="LeaveField" />
<RadioButton Content="RadioButton" HorizontalAlignment="Left" Margin="88,92,0,0" VerticalAlignment="Top"/>
<CheckBox Content="CheckBox" HorizontalAlignment="Left" Margin="252,96,0,0" VerticalAlignment="Top"/>
<ComboBox Name="combo" IsEditable="False" Text="aaa" IsReadOnly="True"
HorizontalAlignment="Left" Margin="337,89,0,0" VerticalAlignment="Top" Width="120"
Focusable="True" GotFocus="ComboBoxGotFocused" >
<ComboBoxItem>January</ComboBoxItem>
<ComboBoxItem>February</ComboBoxItem>
</ComboBox>
<TextBlock HorizontalAlignment="Left" Height="40" Margin="260,184,0,0" TextWrapping="Wrap" Text="Text_Block" VerticalAlignment="Top" Width="257"/>
</Grid>
</Window>
In WPF there are certain 'container' controls that automatically resize their contents and there are some that don't.
Here are some that do not resize their contents (I'm guessing that you are using one or more of these):
StackPanel
WrapPanel
Canvas
TabControl
Here are some that do resize their contents:
Grid
UniformGrid
DockPanel
Therefore, it is almost always preferable to use a Grid instead of a StackPanel unless you do not want automatic resizing to occur. Please note that it is still possible for a Grid to not size its inner controls... it all depends on your Grid.RowDefinition and Grid.ColumnDefinition settings:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" /> <!--<<< Exact Height... won't resize -->
<RowDefinition Height="Auto" /> <!--<<< Will resize to the size of contents -->
<RowDefinition Height="*" /> <!--<<< Will resize taking all remaining space -->
</Grid.RowDefinitions>
</Grid>
You can find out more about the Grid control from the Grid Class page on MSDN. You can also find out more about these container controls from the WPF Container Controls Overview page on MSDN.
Further resizing can be achieved using the FrameworkElement.HorizontalAlignment and FrameworkElement.VerticalAlignment properties. The default value of these properties is Stretch which will stretch elements to fit the size of their containing controls. However, when they are set to any other value, the elements will not stretch.
UPDATE >>>
In response to the questions in your comment:
Use the Grid.RowDefinition and Grid.ColumnDefinition settings to organise a basic structure first... it is common to add Grid controls into the cells of outer Grid controls if need be. You can also use the Grid.ColumnSpan and Grid.RowSpan properties to enable controls to span multiple columns and/or rows of a Grid.
It is most common to have at least one row/column with a Height/Width of "*" which will fill all remaining space, but you can have two or more with this setting, in which case the remaining space will be split between the two (or more) rows/columns. 'Auto' is a good setting to use for the rows/columns that are not set to '"*"', but it really depends on how you want the layout to be.
There is no Auto setting that you can use on the controls in the cells, but this is just as well, because we want the Grid to size the controls for us... therefore, we don't want to set the Height or Width of these controls at all.
The point that I made about the FrameworkElement.HorizontalAlignment and FrameworkElement.VerticalAlignment properties was just to let you know of their existence... as their default value is already Stretch, you don't generally need to set them explicitly.
The Margin property is generally just used to space your controls out evenly... if you drag and drop controls from the Visual Studio Toolbox, VS will set the Margin property to place your control exactly where you dropped it but generally, this is not what we want as it will mess with the auto sizing of controls. If you do this, then just delete or edit the Margin property to suit your needs.
myCanvas is a Canvas control and Parent to all other controllers. This code works to neatly resize to any resolution from 1366 x 768 upward. Tested up to 4k resolution 4096 x 2160
Take note of all the MainWindow property settings (WindowStartupLocation, SizeToContent and WindowState) - important for this to work correctly - WindowState for my user case requirement was Maximized:
XAML:
<Window x:Name="mainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
x:Class="MyApp.MainWindow"
Title="MainWindow" SizeChanged="MainWindow_SizeChanged"
Width="1366" Height="768" WindowState="Maximized" WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<Canvas x:Name="myCanvas" HorizontalAlignment="Left" Height="768" VerticalAlignment="Top" Width="1356">
<Image x:Name="maxresdefault_1_1__jpg" Source="maxresdefault-1[1].jpg" Stretch="Fill" Opacity="0.6" Height="767" Canvas.Left="-6" Width="1366"/>
<Separator Margin="0" Background="#FF302D2D" Foreground="#FF111010" Height="0" Canvas.Left="-811" Canvas.Top="148" Width="766"/>
<Separator Margin="0" Background="#FF302D2D" Foreground="#FF111010" HorizontalAlignment="Right" Width="210" Height="0" Canvas.Left="1653" Canvas.Top="102"/>
<Image x:Name="imgscroll" Source="BcaKKb47i[1].png" Stretch="Fill" RenderTransformOrigin="0.5,0.5" Height="523" Canvas.Left="-3" Canvas.Top="122" Width="580">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="89.093"/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
.cs:
private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
myCanvas.Width = e.NewSize.Width;
myCanvas.Height = e.NewSize.Height;
double xChange = 1, yChange = 1;
if (e.PreviousSize.Width != 0)
xChange = (e.NewSize.Width / e.PreviousSize.Width);
if (e.PreviousSize.Height != 0)
yChange = (e.NewSize.Height / e.PreviousSize.Height);
ScaleTransform scale = new ScaleTransform(myCanvas.LayoutTransform.Value.M11 * xChange, myCanvas.LayoutTransform.Value.M22 * yChange);
myCanvas.LayoutTransform = scale;
myCanvas.UpdateLayout();
}
Well, it's fairly simple to do.
On the window resize event handler, calculate how much the window has grown/shrunk, and use that fraction to adjust 1) Height, 2) Width, 3) Canvas.Top, 4) Canvas.Left properties of all the child controls inside the canvas.
Here's the code:
private void window1_SizeChanged(object sender, SizeChangedEventArgs e)
{
myCanvas.Width = e.NewSize.Width;
myCanvas.Height = e.NewSize.Height;
double xChange = 1, yChange = 1;
if (e.PreviousSize.Width != 0)
xChange = (e.NewSize.Width/e.PreviousSize.Width);
if (e.PreviousSize.Height != 0)
yChange = (e.NewSize.Height / e.PreviousSize.Height);
foreach (FrameworkElement fe in myCanvas.Children )
{
/*because I didn't want to resize the grid I'm having inside the canvas in this particular instance. (doing that from xaml) */
if (fe is Grid == false)
{
fe.Height = fe.ActualHeight * yChange;
fe.Width = fe.ActualWidth * xChange;
Canvas.SetTop(fe, Canvas.GetTop(fe) * yChange);
Canvas.SetLeft(fe, Canvas.GetLeft(fe) * xChange);
}
}
}

Creating a sidebar - flyout like Windows desktop app in WPF

what i am trying to do is create a Desktop application in WPF whose UI is such that a small icon will remain fixed in the center of the left edge of screen and on click(or maybe hover) will slide open a sidebar(like the google desktop bar) running along the left edge of the screen (fixed position, cannot be moved).
do note that what i'm asking for might be like an appbar but i do not want the desktop icons along the left edge to be moved as it happens with an appbar i.e. i do not want it to hog up the desktop spacce....can anyone please suggest me a way out ??
I have implemented a partial solution using this, but i cant get the slide animation and fixed position to workout
Something like this could work:
then of course you could create a slide in animation for the sidebar. This shows (partial) transparency and the switching principle.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="None" Topmost="True" WindowState="Maximized"
AllowsTransparency="True" Background="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Name="rect" Width="100" VerticalAlignment="Stretch" Fill="#99000000" Visibility="Collapsed" />
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Width="32" Height="32" FontSize="16" VerticalAlignment="Center" HorizontalAlignment="Right" Background="White" Click="Button_Click">></Button>
</Grid>
</Window>
C#:
private void Button_Click(object sender, RoutedEventArgs e)
{
if (rect.Visibility == System.Windows.Visibility.Collapsed)
{
rect.Visibility = System.Windows.Visibility.Visible;
(sender as Button).Content = "<";
}
else
{
rect.Visibility = System.Windows.Visibility.Collapsed;
(sender as Button).Content = ">";
}
}
Based on this answer and more answers on this site I made a side bar, I liked the result so i made a repo.
https://github.com/beto-rodriguez/MaterialMenu
you can install it from nuget too.
here is an example
<materialMenu:SideMenu HorizontalAlignment="Left" x:Name="Menu"
MenuWidth="300"
Theme="Default"
State="Hidden">
<materialMenu:SideMenu.Menu>
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<StackPanel Orientation="Vertical">
<Border Background="#337AB5">
<Grid Margin="10">
<TextBox Height="150" BorderThickness="0" Background="Transparent"
VerticalContentAlignment="Bottom" FontFamily="Calibri" FontSize="18"
Foreground="WhiteSmoke" FontWeight="Bold">Welcome</TextBox>
</Grid>
</Border>
<materialMenu:MenuButton Text="Administration"></materialMenu:MenuButton>
<materialMenu:MenuButton Text="Packing"></materialMenu:MenuButton>
<materialMenu:MenuButton Text="Logistics"></materialMenu:MenuButton>
</StackPanel>
</ScrollViewer>
</materialMenu:SideMenu.Menu>
</materialMenu:SideMenu>

WPF numeric up down custom control

I've been needing to use a numeric up-down control for my WPF app. I read a similar question posted here and tried using the one available here > http://bot.codeplex.com/.
I added the references and referenced it in my XAML window
xmlns:lib="clr-namespace:PixelLab.Wpf;assembly=PixelLab.Wpf"
and did this.
<lib:NumericUpDown Name="year"></lib:NumericUpDown>
and keep getting the error: 'nud' is an undeclared namepsace.
I'm very new to WPF so any help would be appreciated.
The Extented WPF Toolkit has one: NumericUpDown
Just combine a TextBox with a veritical fixed-height ScrollBar like this:
<Grid Height="80">
<TextBox x:Name="part_TextBox" Text="{Binding Value,ElementName=part_Scrollbar,StringFormat={}{0:F6},Mode=TwoWay}" MaxLength="11" VerticalAlignment="Center" VerticalContentAlignment="Center" FontSize="24" Height="40"/>
<ScrollBar x:Name="part_Scrollbar" Orientation="Vertical" Minimum="0" Maximum="100" BorderBrush="{x:Null}" SmallChange="0.1" Height="32" Margin="8 4" VerticalAlignment="Stretch" Grid.Column="1" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Right">
<ScrollBar.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="180"/>
<TranslateTransform/>
</TransformGroup>
</ScrollBar.RenderTransform>
</ScrollBar>
</Grid>
Bindings for Maximum & Minimum & SmallChange/Increment are directly avaliable.
Vanilla XAML (no additions or packages) implementation:
<Window x:Class="Spinner.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Spinner"
mc:Ignorable="d"
ResizeMode="CanMinimize" SizeToContent="WidthAndHeight" Title="Scroll Spinner">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- The button exists just to have something other than the spinner be the object of focus. -->
<Button Content="Reset" TabIndex="0"/>
<!-- The spinner is just a scroll bar overlaying a text box (same tab indices). -->
<!-- Only the scroll bar is named (in order to get its value); the text box just relfects the scroll bar's value. -->
<TextBox GotFocus="TextBox_GotFocus" Grid.Row="1" Height="{Binding ElementName=SpinnerScr, Path=ActualHeight}" HorizontalAlignment="Stretch" IsReadOnly="True" TabIndex="1" Text="{Binding ElementName=SpinnerScr, Path=Value, StringFormat={}{0:####0}}" TextAlignment="Center"/>
<ScrollBar x:Name="SpinnerScr" Background="Transparent" Focusable="True" Grid.Row="1" Height="20" LostFocus="SpinnerScr_LostFocus" Margin="0,3" Maximum="999" Orientation="Horizontal" SmallChange="1" TabIndex="1" Visibility="Hidden"/>
<x:Code>
<![CDATA[
void SpinnerScr_LostFocus(object sender, RoutedEventArgs e) {
SpinnerScr.Visibility = Visibility.Hidden;
}
void TextBox_GotFocus(object sender, RoutedEventArgs e) {
SpinnerScr.Visibility = Visibility.Visible;
SpinnerScr.Focus();
}
]]>
</x:Code>
</Grid>
</Window>
using System.Windows;
namespace Spinner {
public partial class MainWindow : System.Windows.Window {
public MainWindow() {
InitializeComponent();
}
}
}
When the scroll bar (or text box) has focus, the scroll elements are visible. On loss of focus, only the text box is visible. Only the scroll bar may be accessed in any code-behind.

Resources