Smooth scroll text in grid column wpf - wpf

I have a Grid with 3 columns. I want to scroll a long text, which I update using a ticker in my second column. I tried textblock, but if my text don't fit in, it will cut my string down. Can you recommend me something to do that.
My code looks like this now:
Here are the three column.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
The textblock which I update
<TextBlock Name="SongTitle" Text="Now onair:" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="1" TextWrapping="NoWrap">
<TextBlock.RenderTransform>
<TranslateTransform x:Name="translate" />
</TextBlock.RenderTransform>
<TextBlock.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
Here is some problem, it starts to scroll not from the column edge...
<DoubleAnimation
From="300" To="0"
Storyboard.TargetName="translate"
Storyboard.TargetProperty="X"
Duration="0:0:8" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>

To start with, instead of using a Grid, you should be using a ScrollViewer. To hide the scrollbars, set HorizontalScrollBarVisibility and VerticalScrollBarVisibility to Hidden. You can then handle mouse wheel events through code-behind to prevent the user from manually scrolling the text.
Of course, it is also possible to use a Grid, but it will give you a bunch of headaches.
To answer your original question, you should be using LayoutTransform instead of RenderTransform.

Related

How to get the position of a control in XAML?

I would like to get the position of a control relative to its parent, or just any kind of position, doesn't really matter. But I want to do this only in XAML without any code behind. I have a grid 5x3, and the button is located in 3rd row and 2nd column. So, basically I would like to get the width of first two rows of the grid, and the width of the first column of the gird.
I want to know the position of the button in pixels, because I want to do translation animation like this:
<DoubleAnimation Storyboard.TargetName="Studio" Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" From="500" To="0" Duration="0:0:2">
but instead of using hardcoded value of 500, I want it to be anything that corresponds to the button's horisontal position in pixels
Something like the XAML below might work for you. It is inspired by your idea of a transparent dummy Rectangle that fills the columns left of the Button.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="dummy" Grid.ColumnSpan="2"/>
<Button Grid.Column="2" VerticalAlignment="Center" Content="Click">
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
<TranslateTransform/>
<ScaleTransform ScaleX="-1"/>
</TransformGroup>
</Button.RenderTransform>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Children[1].X"
To="{Binding ActualWidth, ElementName=dummy}"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
Instead of declaring the RenderTransform as shown above you may perhaps better use a binding converter to invert the value of the dummy Rectangle's ActualWidth property.

Expander getting stuck after IsExpanded set to true by storyboard

So I have an expander that I want to have the normal functionality (open and close with its own button) but I also want a different button to expand it when pressed (this button is in the header of the expander). I'm using a storyboard in an event trigger for the Button.Click which works, but after it is expanded this way the normal button doesn't work, it just stays expanded. My xaml is below, I would really prefer to keep this all in the xaml, I could come up with a way to do it in the codebehind/viewmodel myself.
<Expander x:Name="IndexExpander" IsExpanded="True" Grid.Row="4" Grid.ColumnSpan="5" Margin="10" MaxHeight="150">
<Expander.Triggers>
<EventTrigger SourceName="btnAddIndex" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="IndexExpander" Storyboard.TargetProperty="IsExpanded" BeginTime="0:0:0.25" Duration="0:0:0.20" >
<DiscreteBooleanKeyFrame KeyTime="0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Expander.Triggers>
<Expander.Header>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Text="Indexes" FontWeight="Bold"/>
<!-- Add/Delete Buttons-->
<Grid Margin="10,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" x:Name="btnAddIndex" Command="{Binding AddIndexCommand}" Template="{StaticResource AddButtonTemplate}" IsEnabled="{Binding IsEditable}" Margin="0,0,5,0" />
</Grid>
</StackPanel>
</Expander.Header>
Alright, so for anyone following in my footsteps here's what I did. I got the idea from here, and adapted it until it worked correctly.
<Expander.Triggers>
<EventTrigger SourceName="btnAddCol" RoutedEvent="Button.Click">
<BeginStoryboard x:Name="ColumnExpanderStory">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="ColumnExpander" Storyboard.TargetProperty="IsExpanded">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="ToggleButton.PreviewMouseUp">
<RemoveStoryboard BeginStoryboardName="ColumnExpanderStory" />
</EventTrigger>
</Expander.Triggers>
<Expander.Header>
The problem was that the storyboard overrides any other bindings to the IsExpanded property, so it has to be removed to restore them (read more here). The suggestion was to use the ToggleButton.Checked event to remove the storyboard, but that didn't work for me, only the "Preview" events seemed to have the right timing. I started with PreviewMouseDown, but it would remove the storyboard, then on mouse up toggle the expander, meaning the first click would just flip states back and forth quickly. Using PreviewMouseUp got around that issue.

Access ItemsControl Items and Animate One by One

Today is a good day since I started with WPF, this for a launcher I'm creating.
Using the following code, I managed to get the result to be seen in the screenshot:
<Grid>
<ItemsControl ItemsSource="{Binding Programs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}" Background="Transparent" Foreground="White" Width="128" Height="150" >
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform />
</TransformGroup>
</Button.RenderTransform>
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding Image}" Height="128" />
<ContentPresenter Grid.Row="1" HorizontalAlignment="Center" Margin="3,10" />
<Rectangle Grid.Row="0" Fill="{TemplateBinding Background}" />
<Rectangle Grid.Row="1" Fill="{TemplateBinding Background}" />
</Grid>
</ControlTemplate>
</Button.Template>
<Button.Resources>
<Storyboard SpeedRatio="4" x:Key="MouseEnterStoryboard" x:Name="MouseEnterStoryboard">
<ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" To="#22FFFFFF"></ColorAnimation>
</Storyboard>
<Storyboard SpeedRatio="4" x:Key="MouseLeaveStoryboard" x:Name="MouseLeaveStoryboard">
<ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" To="Transparent"></ColorAnimation>
</Storyboard>
<Storyboard Duration="00:00:00.05" x:Key="MouseClickStoryboard" AutoReverse="True">
<DoubleAnimation To="0.8" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"/>
<DoubleAnimation To="0.8" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"/>
</Storyboard>
<Storyboard x:Key="WindowLoadedStoryboard">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="00:00:01" />
</Storyboard>
</Button.Resources>
<Button.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard Storyboard="{StaticResource MouseEnterStoryboard}" />
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource MouseLeaveStoryboard}" />
</EventTrigger>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource MouseClickStoryboard}" />
</EventTrigger>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard Storyboard="{StaticResource WindowLoadedStoryboard}"></BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Screenshot:
Now, for each item in the list bound to this control, it will create a button.
How would I access this button programmatically, better yet, how would I access one of the Storyboards programatically since assigning a name (x:to them simply won't do the trick it seems...
Also, how can I animate the buttons one by one? Currently they each fade in at exact the same time (# WindowLoadedStoryboard), but I would like to let each button fade in one by one with a short delay, to create a nice effect. How would I achieve this?
Hope someone can answer these 2 questions for me!
Greetings!
Your problem with accessing the elements defined in the DataTemplate is caused because you defined those elements in a DataTemplate... those elements could be rendered in many different types of UI container controls. You can find the solution in the How to: Find DataTemplate-Generated Elements page from MSDN.
You first need to get hold of the relevant container control that contains the item that has had that DataTemplate applied to it. Next, you need to get the ContentPresenter from that container control and then you can get the DataTemplate from ContentPresenter. Finally, you can access the named elements from the DataTemplate. From the linked page:
// Getting the currently selected ListBoxItem
// Note that the ListBox must have
// IsSynchronizedWithCurrentItem set to True for this to work
ListBoxItem myListBoxItem = (ListBoxItem)(myListBox.ItemContainerGenerator.
ContainerFromItem(myListBox.Items.CurrentItem));
// Getting the ContentPresenter of myListBoxItem
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem);
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
TextBlock myTextBlock =
(TextBlock)myDataTemplate.FindName("textBlock", myContentPresenter);
// Do something to the DataTemplate-generated TextBlock
MessageBox.Show("The text of the TextBlock of the selected list item: " +
myTextBlock.Text);

XAML animation of height of control with dynamic content

I have a panel that should be minimized unless the user hovers the mouse over the panel. It is implemented using a storyboard that lets the height of the panel grow when the use puts the mouse over the control. At the moment the target height is hard coded to 400 which is a bad solution as the content of the panel will be different each time the application starts (it is static during execution).
How do you create an animation that lets the panel grow to the size of the current content?
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="500" Width="525">
<Grid>
<Border Margin="10,0" Background="LightGray" HorizontalAlignment="Left" VerticalAlignment="Top" CornerRadius="0,0,8,8">
<Border.Effect>
<DropShadowEffect Opacity="0.5"/>
</Border.Effect>
<Border.Triggers>
<EventTrigger RoutedEvent="Border.MouseEnter">
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height"
From="25"
To="400"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Border.MouseLeave">
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height"
From="400"
To="25"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<StackPanel Margin="5">
<TextBlock Height="25" Text="My items panel" />
<ListBox MinWidth="150" MinHeight="100" ItemsSource="{Binding MyItems}" />
</StackPanel>
</Border>
</Grid>
Edit: I have tried with binding to the Height of the StackPanel but that didn't really help as it didn't take the margins of the stackpanel into account thus making the panel shorter than needed.
<DoubleAnimation Storyboard.TargetProperty="Height"
From="{Binding ElementName=NameOfStackPanel, Path=ActualHeight}"
To="25"
Duration="0:0:0.2" />
You could create a converter to handle adding the margins to the ActualHeight of your StackPanel. You could even use a multivalue convertor so you could bind the margin too and not have to hardcode a fudge factor. Finally, you could probably wrap your stackpanel in another panel (without margins) and bind to the height of that instead.

Bind to animated property (WPF)

Does anybody knows, if there is a way to use a property that is animated as source for a binding?
As far as I found out, is that an animation doesn't "really" set the value on the property, and therefore doesn't fire the changed events, which is needed to trigger the binding.
tia
Martin
I don't know if what you are saying is exactly true. In the XAML below, the TextBlock will display the Width of the Rectangle. When you click on the Rectangle, the Width property is animated from 50 to 300. Along every increment, the TextBlock changes in value. Am I not understanding your question?
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ElementName=Rect,Path=Width}" Grid.Row="0" />
<Rectangle Grid.Row="1"
Name="Rect"
Height="30"
Width="50"
Fill="Blue"
HorizontalAlignment="Left">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseLeftButtonDown">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
From="50"
To="300"
Duration="0:0:10"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Grid>
Animation does actually really change the value, it does call the property changed callback you pass to DependecyProperty.Register and it does cause a layout/render pass id needed.

Resources