I have two TextBoxes and a ScrollBar. With the scrollbar I want to scroll both TextBoxes. The maximum value of the scrollbar is the maximum of the maximum values of the two textbox scrollbars.
My problem is that if the text inside a TextBox is not bigger than the textbox itself, the text won't scroll. How can I force the text to be scrollable?
Here is my code:
<Window x:Class="HorizontalScrollViewerTest.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Loaded="MainWindow_OnLoaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
x:Name="UpperTextBox"
Text="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
HorizontalScrollBarVisibility="Hidden"/>
<TextBox Grid.Row="1"
x:Name="LowerTextBox"
Text="abc"
HorizontalScrollBarVisibility="Hidden"/>
<ScrollBar Grid.Row="2"
x:Name="ScrollBar"
Orientation="Horizontal"
Value="{Binding ScrollValue, RelativeSource={RelativeSource AncestorType=Window}}"/>
</Grid>
using System;
using System.Windows;
namespace HorizontalScrollViewerTest
{
public partial class MainWindow
{
public static readonly DependencyProperty ScrollValueProperty = DependencyProperty.Register(
"ScrollValue", typeof(int), typeof(MainWindow), new PropertyMetadata(default(int), ScrollValueChanged));
private static void ScrollValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow).UpperTextBox.ScrollToHorizontalOffset((int)e.NewValue);
(d as MainWindow).LowerTextBox.ScrollToHorizontalOffset((int)e.NewValue);
}
public int ScrollValue
{
get => (int) GetValue(ScrollValueProperty);
set => SetValue(ScrollValueProperty, value);
}
public MainWindow()
{
InitializeComponent();
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
var maxExtent = Math.Max(UpperTextBox.ExtentWidth, LowerTextBox.ExtentWidth);
ScrollBar.Maximum = Math.Max(ScrollBar.ActualWidth, maxExtent) - ScrollBar.ActualWidth;
ScrollBar.ViewportSize = ScrollBar.ActualWidth;
}
}
}
Can't you just wrap these two TextBoxes in a ScrollViewer?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ScrollViewer CanContentScroll="true" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden" Grid.Column="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
x:Name="UpperTextBox"
Text="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"/>
<TextBox Grid.Row="1"
x:Name="LowerTextBox"
Text="abc"/>
</Grid>
</ScrollViewer>
</Grid>
If you cannot wrap the TextBoxes you can apply a Margin to them instead of using ScrollToHorizontalOffset:
private static void ScrollValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow).UpperTextBox.Margin = new Thickness(-1 * (int)e.NewValue, 0, 0, 0);
(d as MainWindow).LowerTextBox.Margin = new Thickness(-1 * (int)e.NewValue, 0, 0, 0);
//(d as MainWindow).UpperTextBox.ScrollToHorizontalOffset((int)e.NewValue * 100);
//(d as MainWindow).LowerTextBox.ScrollToHorizontalOffset((int)e.NewValue * 100);
}
You should also compute the ScrollBar.Maximum and the ScrollBar.ViewportSize each time one of the component is resized:
<ScrollBar Grid.Row="2" SizeChanged="MainWindow_OnLoaded" [...]/>
Related
I need to create a scrollviewer with X amount of items, i made the code that create a border with an image and sets into a grid, thats the code:
public partial class MainWindow : Window
{
panel panels = new panel();
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int col = -1;
int actual_row =0;
for (int i = 0; i != 20;i++)
{
col++;
Grid g = panels.create_panel();
scroll.Children.Add(g);
g.SetValue(Grid.RowProperty, actual_row);
g.SetValue(Grid.ColumnProperty, col);
if (col == 3)
{
actual_row++;
col = -1;
//aad row
RowDefinition newrow = new RowDefinition();
scroll.RowDefinitions.Add(newrow);
}
}
}
}
and her is the XAML:
<Window x:Class="pretyListbox.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:pretyListbox"
mc:Ignorable="d"
Background="#282828"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style x:Key="item" TargetType="Grid">
</Style>
</Window.Resources>
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
>
<Grid x:Name="scroll">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
</ScrollViewer>
<Button Height="100" Width="100" Click="Button_Click" Content="gen" Margin="0,0,3,1" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
</Window>
I put an image of the app working:
App working
And there is the lag:Lagged app
the lag only appears the first time it get resized or scrolled, but isnt about time bc i wait 1 min and it get lagg equally.
If someone can tell me how to repair that or charge the grid after with an async methot i will be very greatefull. Thnx.
Use ItemControl and Bind all items to ItemSource, It will create Vertical scroll bar if required
Please look at below example
<ItemsControl x:Name="CountryCodeFlagControl"
ItemsSource="{Binding SourceList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<Border>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Value}">
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
i resolve my answer, i just put an asynchronous methot and now it works perfectly every time.
The code :
private async void Button_Click(object sender, RoutedEventArgs e)
{
int col = -1;
int actual_row =0;
for (int i = 0; i != 100; i++)
{
Task task = Task.Factory.StartNew(() =>
{
col++;
Dispatcher.Invoke(() =>
{
Grid g = panels.create_panel();
scroll.Children.Add(g);
g.SetValue(Grid.RowProperty, actual_row);
g.SetValue(Grid.ColumnProperty, col);
if (col == 3)
{
actual_row++;
col = -1;
//aad row
RowDefinition newrow = new RowDefinition();
scroll.RowDefinitions.Add(newrow);
}
});
});
await Task.Delay(100);
await task;
}
}
In my sample wpf app i have a picture of a house onto which i have drawn 4 humidity sensors using ellipse in xaml. To draw the sensors in the correct location i have used grid columns and rows. To display the sensor values i created a HumidityView which draws a rectangle and a dockpanel containing the actual measured humidity value.
<Window x:Class="WpfHouseExample.Views.MainView"
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:WpfHouseExample.Views"
mc:Ignorable="d"
Background="Transparent"
Title="MainView" Height="450" Width="300">
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="2" Grid.Column="0" Grid.RowSpan="2" x:Name="Humidity1" Margin="0,0,0,2" HorizontalAlignment="Right"/>
<ContentControl Grid.Row="6" Grid.Column="0" Grid.RowSpan="2" x:Name="Humidity2" Margin="0,0,0,2"/>
<ContentControl Grid.Row="2" Grid.Column="5" Grid.RowSpan="2" x:Name="Humidity3" Margin="0,0,0,2"/>
<ContentControl Grid.Row="6" Grid.Column="5" Grid.RowSpan="2" x:Name="Humidity4" Margin="0,0,0,2"/>
<Image Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="11" Source="pack://application:,,,/Images/House.png" Margin="20"/>
<Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="3" Grid.RowSpan="2"/>
<Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="3" Grid.RowSpan="2"/>
<Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="5" Grid.RowSpan="2"/>
<Ellipse Width="20" Height="20" Fill="LightGreen" Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="5" Grid.RowSpan="2"/>
<Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="1" Grid.Row="3" Stretch="Fill" X2="1" Y2="1"/>
<Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="1" Grid.Row="6" Stretch="Fill" X2="1" Y1="1"/>
<Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="4" Grid.Row="3" Stretch="Fill" X2="1" Y1="1"/>
<Line Stroke="LightGreen" StrokeThickness="2" Grid.Column="4" Grid.Row="6" Stretch="Fill" X2="1" Y2="1"/>
</Grid>
</Window>
My question is about drawing lines from the sensor to the view control. Now i figured out to use the grid and draw horizontal lines in the grid. What i really would like to do is draw diagonal lines from a sensor to view control.
I have found diagramming solutions but that imlementations use only a canvas which does not support positioning of the controls like a grid does.
What is the best way to do this?
[Edit => code in question is updated with option to draw diagonal lines in grid]
You could create a custom control in order to draw a line between any two controls that are located within a common parent element.
The custom control would take the common parent, and the two elements to be connected as parameters, then get their positon and size in order to compute the correct start and end points for a line between them.
In my example code, I draw the line from the middle of the elements, but given the element rects, you can implement any other logic to determine the desired line end points.
Note the example is just a small demo and might neither be efficient nor completely usable.
Custom Control code:
/// <summary>
/// Custom Line control to draw a line between two other controls
/// </summary>
public class LineConnectorControl : Control
{
static LineConnectorControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LineConnectorControl), new FrameworkPropertyMetadata(typeof(LineConnectorControl)));
}
#region Target Properties for Visual Line
public double X1
{
get { return (double)GetValue(X1Property); }
set { SetValue(X1Property, value); }
}
// Using a DependencyProperty as the backing store for X1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty X1Property =
DependencyProperty.Register("X1", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));
public double X2
{
get { return (double)GetValue(X2Property); }
set { SetValue(X2Property, value); }
}
// Using a DependencyProperty as the backing store for X2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty X2Property =
DependencyProperty.Register("X2", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));
public double Y1
{
get { return (double)GetValue(Y1Property); }
set { SetValue(Y1Property, value); }
}
// Using a DependencyProperty as the backing store for Y1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty Y1Property =
DependencyProperty.Register("Y1", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));
public double Y2
{
get { return (double)GetValue(Y2Property); }
set { SetValue(Y2Property, value); }
}
// Using a DependencyProperty as the backing store for Y2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty Y2Property =
DependencyProperty.Register("Y2", typeof(double), typeof(LineConnectorControl), new PropertyMetadata(0d));
#endregion
#region Source Elements needed to compute Visual Line
// Positions are computed relative to this element
public FrameworkElement PositionRoot
{
get { return (FrameworkElement)GetValue(PositionRootProperty); }
set { SetValue(PositionRootProperty, value); }
}
// This is the starting point of the line
public FrameworkElement ConnectedControl1
{
get { return (FrameworkElement)GetValue(ConnectedControl1Property); }
set { SetValue(ConnectedControl1Property, value); }
}
// This is the ending point of the line
public FrameworkElement ConnectedControl2
{
get { return (FrameworkElement)GetValue(ConnectedControl2Property); }
set { SetValue(ConnectedControl2Property, value); }
}
// Using a DependencyProperty as the backing store for PositionRoot. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionRootProperty =
DependencyProperty.Register("PositionRoot", typeof(FrameworkElement), typeof(LineConnectorControl), new FrameworkPropertyMetadata(new PropertyChangedCallback(ElementChanged)));
// Using a DependencyProperty as the backing store for ConnectedControl1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ConnectedControl1Property =
DependencyProperty.Register("ConnectedControl1", typeof(FrameworkElement), typeof(LineConnectorControl), new FrameworkPropertyMetadata(new PropertyChangedCallback(ElementChanged)));
// Using a DependencyProperty as the backing store for ConnectedControl2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ConnectedControl2Property =
DependencyProperty.Register("ConnectedControl2", typeof(FrameworkElement), typeof(LineConnectorControl), new FrameworkPropertyMetadata(new PropertyChangedCallback(ElementChanged)));
#endregion
#region Update logic to compute line coordinates based on Source Elements
private static void ElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = (LineConnectorControl)d;
self.UpdatePositions();
var fr = (FrameworkElement)e.NewValue;
fr.SizeChanged += self.ElementSizeChanged;
}
private void ElementSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdatePositions();
}
private void UpdatePositions()
{
if (PositionRoot != null && ConnectedControl1 != null && ConnectedControl2 != null)
{
Rect rect1 = GetRootedRect(ConnectedControl1);
Rect rect2 = GetRootedRect(ConnectedControl2);
X1 = rect1.Location.X + (rect1.Width / 2);
Y1 = rect1.Location.Y + (rect1.Height / 2);
X2 = rect2.Location.X + (rect2.Width / 2);
Y2 = rect2.Location.Y + (rect2.Height / 2);
}
}
private Rect GetRootedRect(FrameworkElement childControl)
{
var rootRelativePosition = childControl.TransformToAncestor(PositionRoot).Transform(new Point(0, 0));
return new Rect(rootRelativePosition, new Size(childControl.ActualWidth, childControl.ActualHeight));
}
#endregion
}
Custom Control visual style in Generic.xaml
<Style TargetType="{x:Type local:LineConnectorControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LineConnectorControl}">
<Line X1="{TemplateBinding X1}" X2="{TemplateBinding X2}" Y1="{TemplateBinding Y1}" Y2="{TemplateBinding Y2}" Stroke="Red" StrokeThickness="2"></Line>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage example
<Grid Name="parentGrid">
<Grid Name="myGrid" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="50"/>
<ColumnDefinition Width="Auto" MinWidth="50"/>
<ColumnDefinition Width="Auto" MinWidth="50"/>
<ColumnDefinition Width="Auto" MinWidth="50"/>
<ColumnDefinition Width="Auto" MinWidth="50"/>
<ColumnDefinition Width="Auto" MinWidth="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border x:Name="Humidity1" Grid.Row="0" Grid.Column="4" MinWidth="30" Background="Yellow" HorizontalAlignment="Right"/>
<Border x:Name="Humidity2" Grid.Row="3" Grid.Column="0" Grid.RowSpan="2" Background="Green"/>
</Grid>
<!--connecting line-->
<local:LineConnectorControl PositionRoot="{Binding ElementName=parentGrid}" ConnectedControl1="{Binding ElementName=Humidity1}" ConnectedControl2="{Binding ElementName=Humidity2}"/>
</Grid>
I implemented the solution for this question, in a window with XAML given below. I am trying to make a scrolling marquee text effect for a label:
<Window x:Class="WpfMarqueeText.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfMarqueeText="clr-namespace:WpfMarqueeText"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Background="Aqua">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="300"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Margin="5,3,5,3" Fill="#b933ad"/>
<Label Grid.Column="0" Content="Z" Foreground="White" FontFamily="HelveticaBold" FontSize="150" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5,3,5,3"/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Some Info:" FontFamily="HelveticaBold" FontSize="18" FontWeight="Bold" Margin="5,3,5,3"/>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" x:Name="stack">
<StackPanel.Resources>
<wpfMarqueeText:NegatingConverter x:Key="NegatingConverter" />
<Storyboard x:Key="slide">
<DoubleAnimation From="0" To="{Binding Width, ElementName=canvas, Converter={StaticResource NegatingConverter}}" Duration="00:00:10"
Storyboard.TargetProperty="X"
Storyboard.TargetName="transferCurreny"
RepeatBehavior="Forever"/>
</Storyboard>
</StackPanel.Resources>
<StackPanel.RenderTransform>
<TranslateTransform x:Name="transferCurreny" X="0"/>
</StackPanel.RenderTransform>
<StackPanel.Triggers>
<EventTrigger RoutedEvent="StackPanel.Loaded">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
<EventTrigger RoutedEvent="StackPanel.SizeChanged">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
</StackPanel.Triggers>
<Canvas x:Name="canvas" Width="{Binding ActualWidth, ElementName=stack}">
<Label FontFamily="HelveticaBold" FontSize="18" Margin="5,3,5,3" x:Name="Label1" Content="Blah blah blah" Canvas.Left="0"/>
<Label Name="Label2" Content="{Binding Content, ElementName=Label1}" FontFamily="HelveticaBold" FontSize="18" Margin="5,3,5,3" Canvas.Left="{Binding ActualWidth, ElementName=stack}"/>
</Canvas>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Grid>
</Window>
You must also define the NegatingConverter class in the code-behind:
public class NegatingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double)
{
return -((double)value);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double)
{
return +(double)value;
}
return value;
}
}
This produces the desired efect, however the text animation scrolls over the other UI elements as seen in the images below (sorry, I don't have enough rep to post images):
http://tinypic.com/r/df8zeu/9
http://tinypic.com/r/2inc3r/9
So, is there any way to fix the animation so that the text only scrolls within the boundary of the grid column it is contained within, or within the boundaries of the label itself? Thanks for any help!
This is a quick and dirty-solution:
Change your Label to
<Label Grid.Row="0" Grid.Column="0" Content="Some Info:" FontFamily="HelveticaBold" FontSize="18" FontWeight="Bold" Margin="5,3,5,3" Panel.ZIndex="99" Background="Aqua"/>
The Panel.ZIndex brings the Label to front. And making the Background not Transparent gives the desired look. The boundries are still not perfect, but this should give you a clue on how to handle Layers
The article linked by Akanksha in response to my OP shows how to create a user control that produces a clean, scrolling text effect. You can also specify 4 different scroll directions, left <-> right and up <-> down. I'll give my implementation here for others:
XAML for MarqueeTextUserControl:
<UserControl x:Class="AaronLuna.Common.UI.UserControls.MarqueeTextUserControl"
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"
mc:Ignorable="d" Loaded="UserControl_Loaded">
<Canvas ClipToBounds="True" Name="CanvasMain">
<TextBlock Name="TextBlockMain"/>
</Canvas>
</UserControl>
Code for MarqueeTextUserControl:
namespace AaronLuna.Common.UI.UserControls
{
public partial class MarqueeTextUserControl
{
public MarqueeTextUserControl()
{
InitializeComponent();
CanvasMain.Height = Height;
CanvasMain.Width = Width;
}
public ScrollDirection ScrollDirection { get; set; }
public double ScrollDurationInSeconds { get; set; }
public String Text { set { TextBlockMain.Text = value; }}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
ScrollText(ScrollDirection);
}
public void ScrollText(ScrollDirection scrollDirection)
{
switch (scrollDirection)
{
case ScrollDirection.LeftToRight:
LeftToRightMarquee();
break;
case ScrollDirection.RightToLeft:
RightToLeftMarquee();
break;
case ScrollDirection.TopToBottom:
TopToBottomMarquee();
break;
case ScrollDirection.BottomToTop:
BottomToTopMarquee();
break;
}
}
private void LeftToRightMarquee()
{
double height = CanvasMain.ActualHeight - TextBlockMain.ActualHeight;
TextBlockMain.Margin = new Thickness(0, height/2, 0, 0);
var doubleAnimation = new DoubleAnimation
{
From = -TextBlockMain.ActualWidth,
To = CanvasMain.ActualWidth,
RepeatBehavior = RepeatBehavior.Forever,
Duration = new Duration(TimeSpan.FromSeconds(ScrollDurationInSeconds))
};
TextBlockMain.BeginAnimation(Canvas.LeftProperty, doubleAnimation);
}
private void RightToLeftMarquee()
{
double height = CanvasMain.ActualHeight - TextBlockMain.ActualHeight;
TextBlockMain.Margin = new Thickness(0, height/2, 0, 0);
var doubleAnimation = new DoubleAnimation
{
From = -TextBlockMain.ActualWidth,
To = CanvasMain.ActualWidth,
RepeatBehavior = RepeatBehavior.Forever,
Duration = new Duration(TimeSpan.FromSeconds(ScrollDurationInSeconds))
};
TextBlockMain.BeginAnimation(Canvas.RightProperty, doubleAnimation);
}
private void TopToBottomMarquee()
{
double width = CanvasMain.ActualWidth - TextBlockMain.ActualWidth;
TextBlockMain.Margin = new Thickness(width/2, 0, 0, 0);
var doubleAnimation = new DoubleAnimation
{
From = -TextBlockMain.ActualHeight,
To = CanvasMain.ActualHeight,
RepeatBehavior = RepeatBehavior.Forever,
Duration = new Duration(TimeSpan.FromSeconds(ScrollDurationInSeconds))
};
TextBlockMain.BeginAnimation(Canvas.TopProperty, doubleAnimation);
}
private void BottomToTopMarquee()
{
double width = CanvasMain.ActualWidth - TextBlockMain.ActualWidth;
TextBlockMain.Margin = new Thickness(width/2, 0, 0, 0);
var doubleAnimation = new DoubleAnimation
{
From = -TextBlockMain.ActualHeight,
To = CanvasMain.ActualHeight,
RepeatBehavior = RepeatBehavior.Forever,
Duration = new Duration(TimeSpan.FromSeconds(ScrollDurationInSeconds))
};
TextBlockMain.BeginAnimation(Canvas.BottomProperty, doubleAnimation);
}
}
public enum ScrollDirection
{
LeftToRight,
RightToLeft,
TopToBottom,
BottomToTop
}
}
Client XAML:
<UserControl x:Class="MarqueeTextExampleUserControl"
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:userControls="clr-namespace:AaronLuna.Common.UI.UserControls;assembly=AaronLuna.Common"
mc:Ignorable="d">
<DockPanel>
<Label Content="Some Info:"/>
<userControls:MarqueeTextUserControl x:Name="MarqueeTextBlock"/>
</DockPanel>
</UserControl>
Client Code:
MarqueeTextBlock.Text = "Blah blah blah";
MarqueeTextBlock.ScrollDirection = ScrollDirection.RightToLeft;
MarqueeTextBlock.ScrollDurationInSeconds = 10;
I am having no luck finding the issue of data binding not working. I have two user controls. The user control that uses the obervablecollection works fine. The user control bound to an object doesnt. If i assign the value to the text the value does appear. During debugging I can verify that the values are correct.
This logic is following Paul Sheriff and a few posts from this web site.
My coworkers dont program in C# so they cant help. Im missing something but have no idea what it is.
ViewModel class that inherits from INotifyPropertyChanged:
ParameterSettings _ps;
public ParameterSettings DetailData
{
get { return _ps; }
set
{
_ps = value;
RaisePropertyChanged("DetailData");
}
}
public async Task GetParameters()
{
var pm = new ParameterManager();
DetailData = new ParameterSettings();
await pm.GetLoginCredentials(_ps);
}
this is the code the user control.
ViewModels.ParameterSettingsVm _viewModel;
public ParameterSettingsUc()
{
this.InitializeComponent();
_viewModel = (ParameterSettingsVm)Resources["viewModel"];
var bounds = Window.Current.Bounds;
this.CancelBtn.Width = bounds.Width * .5;
this.SaveBtn.Width = bounds.Width * .5;
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.GetParameters();
//UserNameBx.Text = _viewModel.DetailData.UserLogin; //textbox gets filled in.
}
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SiteManager.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:VM="using:SiteManager.ViewModels"
x:Class="SiteManager.Views.ParameterSettingsUc"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
Loaded="UserControl_Loaded">
<UserControl.Resources>
<VM:ParameterSettingsVm x:Key="viewModel"></VM:ParameterSettingsVm>
</UserControl.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<TextBox Header="Login:" VerticalAlignment="Center" Margin="2,10,0,0" Grid.Row="0" x:Name="UserNameBx" Text="{Binding Path=DetailData.UserLogin, Mode=TwoWay, UpdateSourceTrigger=Default}" >
<TextBox.DataContext>
<VM:ParameterSettingsVm/>
</TextBox.DataContext>
</TextBox>
I would change is it to make the ViewModel a property.
ViewModels.ParameterSettingsVm _viewModel {get;set;}
public ParameterSettingsUc()
{
this.InitializeComponent();
_viewModel = (ParameterSettingsVm)Resources["viewModel"];
var bounds = Window.Current.Bounds;
this.CancelBtn.Width = bounds.Width * .5;
this.SaveBtn.Width = bounds.Width * .5;
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
await _viewModel.GetParameters();
//UserNameBx.Text = _viewModel.DetailData.UserLogin; //textbox gets filled in.
}
and then I would set the _viewModel as the data context for the textBox. Oh and set the dataContext for the usercontrol to self, like this.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SiteManager.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:VM="using:SiteManager.ViewModels"
x:Class="SiteManager.Views.ParameterSettingsUc"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Loaded="UserControl_Loaded">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<TextBox Header="Login:" VerticalAlignment="Center"
Margin="2,10,0,0" Grid.Row="0" x:Name="UserNameBx"
Text="{Binding Path=DetailData.UserLogin, Mode=TwoWay, UpdateSourceTrigger=Default}"
DataContext={Binding _viewModel}>
</TextBox>
This may not be what you're trying to do though. I just assumed since you're creating _viewModel you would want to use it.
From microsoft virtual academy. Used x:bind its faster and less verbose.
mva.microsoft.com/en-US/training-courses/windows-10-data-binding-14579. Each class property i made into a INotfiyChange property of the vm.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SiteManager.Views"
xmlns:VM="using:SiteManager.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:diag="using:System.Diagnostics"
x:Class="SiteManager.Views.ParameterSettingsUc"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
Loaded="UserControl_Loaded" >
<UserControl.DataContext>
<VM:ParameterSettingsVm></VM:ParameterSettingsVm>
</UserControl.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="70"/>
<RowDefinition Height="70"/>
<RowDefinition Height="70"/>
</Grid.RowDefinitions>
<TextBox Header="Login:" VerticalAlignment="Center" Margin="2,10,0,0" Grid.Row="0" x:Name="UserNameBx" Text="{x:Bind Path=_viewModel.UserLogin, Mode=TwoWay }" > </TextBox>
<TextBox Header="Password:" VerticalAlignment="Center" Margin="1" Grid.Row="1" x:Name="PasswordBx" Text="{x:Bind Path=_viewModel.UserPassword, Mode=TwoWay }"> </TextBox>
<TextBox Header="Mature Key:" VerticalAlignment="Center" Margin="1" Grid.Row="2" x:Name="MatureKeyBx" Text="{x:Bind Path=_viewModel.MatureKey, Mode=TwoWay }"> </TextBox>
public sealed partial class ParameterSettingsUc : UserControl
{
ParameterSettingsVm _viewModel { get; set; } = new ParameterSettingsVm();
string _userLogin;
public string UserLogin
{
get { return _userLogin; }
set
{
_userLogin = value;
RaisePropertyChanged("UserLogin");
}
}
GridSplitterControl.xaml
<sdk:GridSplitter x:Class="JustLogIt.Common.GridSplitterControl"
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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d">
<sdk:GridSplitter.Template>
<ControlTemplate TargetType="sdk:GridSplitter">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<telerik:RadButton Grid.Row="0"
Width="10"
Height="20"
HorizontalAlignment="Center"
Click="Click"
Cursor="Hand" />
<Border Grid.Row="1"
BorderBrush="LightGray"
BorderThickness="5" />
</Grid>
</ControlTemplate>
</sdk:GridSplitter.Template>
</sdk:GridSplitter>
GridSplitterControl.xaml.cs
public partial class GridSplitterControl : GridSplitter
{
GridLength AutoSize = new GridLength(1.0, GridUnitType.Auto);
GridLength ZeroSize = new GridLength(0.0);
public ColumnDefinition Left{ set; get;}
public GridSplitterControl()
{
InitializeComponent();
}
public GridSplitterControl(ColumnDefinition ColLeft)
{
InitializeComponent();
Left = ColLeft;
}
public void Click(object sender, RoutedEventArgs e)
{
if (Left != null)
{
Left.MinWidth = 10.0;
if ((Left.Width.Value + 10) == Left.MinWidth)
Left.Width = AutoSize;
else if (Left.Width.Value != AutoSize.Value)
Left.Width = AutoSize;
else Left.Width = ZeroSize;
}
//ClickNotify(sender, e);
}
public event EventHandler ClickCompleted;
private void ClickNotify(object senderAIF, RoutedEventArgs eAIF)
{
if (ClickCompleted != null)
ClickCompleted(senderAIF, eAIF);
}
}
The Click event can work, but I have no idea to set the Left(ColumnDefinition) in xaml file which used this Element.
<Grid x:Name="LayoutRoot1"
Grid.Row="1"
Margin="5"
Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LvCol_10" Width="*" />
<ColumnDefinition x:Name="LvCol_11" Width="*" />
</Grid.ColumnDefinitions>
<Mylayout:GridSplitterControl x:Name="GridSplitter_Left"
Grid.RowSpan="7"
Grid.Column="0"
Width="10"
VerticalAlignment="Stretch"
IsEnabled="True"
Left=? /> < !-- How to set "LvCol_11" in here? -->
</Grid>`
It seems like that you want to set the grid.row as property which is used when the button click.
Maybe you can try this...
GridSplitterControl.xaml.cs
private void UI_Loaded(object sender, RoutedEventArgs e)
{
if (sender is GridSplitter)
{
Grid ParentGrid = this.ParentOfType<Grid>();
SetLocation(ParentGrid);
}
}
public void SetLocation(Grid p_Layout)
{
int index = Grid.GetColumn(this);
ColumnDefinition ColLocation = p_Layout.ColumnDefinitions[index];
ColLocation.MinWidth = 10;
Left = ColLocation;
}
Becarefully, besure your element's parent(Up one level) is grid.
If parent is stackPanel, border, or rectangle, it will be not working, maybe.