I'm working on a C#/XAML Metro style app for Windows 8. The XAML in WinRT does not have a "tab" control. However, I'm trying to emulate the way a result in the Windows 8 store looks. For instance, this image shows "Overview", "Details", and "Reviews" tabs:
How do I create these?
A RadioButton seems to make sense. I figured I could use the GroupName to ensure only one item is selected. But if I use a RadioButton, I don't know how to make the selected item look dark gray while makig the other options light gray. Can someone show me the XAML of a RadioButton that does not show the little checked thingy? And also is dark gray when selected and light gray when not selected.
Thank you so much!
Here is the style to use for radio buttons to make them look/work like tabs:
<!-- Style for radio buttons used as tab control -->
<Style x:Key="TabRadioButtonStyle" TargetType="RadioButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Unchecked">
<Storyboard>
<ColorAnimation Duration="0" To="Gray" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TabButtonText" />
</Storyboard>
</VisualState>
<VisualState x:Name="Indeterminate">
<Storyboard>
<ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TabButtonText" />
</Storyboard>
</VisualState>
<VisualState x:Name="Checked">
<Storyboard>
<ColorAnimation Duration="0" To="Black" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TabButtonText" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="TabButtonText" Text="{TemplateBinding Content}" Style="{StaticResource GroupHeaderTextStyle}" HorizontalAlignment="Left"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then define a grid to hold the tab stack panel and a frame to hold the content associated with each tab. Use Click event on the radio buttons to navigate the frame to the appropriate pages for each "tab".
<Grid Grid.Row="1"
Margin="120,0,56,56">
<!-- Row 1 to hold the "Tabs", Row 2 to hold the content -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<RadioButton x:Name="Tab1" Content="Tab1" Style="{StaticResource TabRadioButtonStyle}" IsChecked="True" Click="Tab1_Clicked" />
<RadioButton x:Name="Tab2" Content="Tab2" Style="{StaticResource TabRadioButtonStyle}" IsChecked="False" Click="Tab2_Clicked" Margin="30,0,0,0" />
<RadioButton x:Name="Tab3" Content="Tab3" Style="{StaticResource TabRadioButtonStyle}" IsChecked="False" Click="Tab3_Clicked" Margin="30,0,0,0"/>
</StackPanel>
<Frame x:Name="ContentFrame" Margin="0,20,0,0" Grid.Row="1" Background="{StaticResource SandstormBackgroundBrush}" Loaded="ContentFrame_Loaded" />
</Grid>
Styling a ListBox is preferable to styling a radio button group.
The following code uses a ListBox with a horizontal stack panel to create the tab item header. A ContentControl displays the tab content as a user control.
I've only tested this with WPF, but hopefully it will work on WinRT.
<Page.Resources>
<Style TargetType="ListBoxItem">
<!-- disable default selection highlight -->
<!-- Style.Resources is not supported in WinRT -->
<!--<Style.Resources>
--><!-- SelectedItem with focus --><!--
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent" />
--><!-- SelectedItem without focus --><!--
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Transparent" />
</Style.Resources>-->
<!--Setter Property="FocusVisualStyle" is not supported in WinRT -->
<!--<Setter Property="FocusVisualStyle" Value="{x:Null}" />-->
</Style>
<Style x:Key="TitleStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="LightGray"/>
<!--Style.Triggers is not supported in WinRT-->
<!--<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding Path=IsSelected,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}">
<Setter Property="Foreground" Value="DarkGray"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>-->
</Style>
</Page.Resources>
<Grid>
<Grid.DataContext>
<ViewModel:TestPage/>
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox x:Name="tabListBox" Grid.Row="0" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
BorderBrush="{x:Null}" BorderThickness="0">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" Margin="5"
Style="{StaticResource TitleStyle}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Row="1" Content="{Binding SelectedItem.Content}"/>
</Grid>
View model
public class MyTabViewModel : INotifyPropertyChanged
{
public MyTabViewModel()
{
Items =
new List<MyTabItem>
{
new MyTabItem
{
Title = "Overview",
Content = new UserControl1()
},
new MyTabItem
{
Title = "Detail",
Content = new UserControl2()
},
new MyTabItem
{
Title = "Reviews",
Content = new UserControl3()
},
};
}
public IEnumerable<MyTabItem> Items { get; private set; }
private MyTabItem _selectedItem;
public MyTabItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class MyTabItem
{
public string Title { get; set; }
public UserControl Content { get; set; }
}
The FlipView control might meet your needs. Sample.
I used FlipView control as well, but I created a separate templated control which is inherited from FlipView.
The main idea is to override default FlipView ControlTemplate: I added a ListBox which represents tab headers and removed "Next" and "Previous" FlipView buttons.
I can share source code if you need more details about my implementation.
Related
I have a listbox that loads it's items with Foreground color set to red. What I'd like to do is: upon selecting an item with the mouse, change the foreground color of SelectedItem to black, but make the change persistent so that after deselecting the item, color remains black. Incidentally I want to implement this as a way of showing 'read items' to the user.
Essentially I want something like an implementation of the common property trigger like the code below, but not have the style revert after deselection. I've played around with event triggers as well without much luck.
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="Foreground" Value="Black" /> //make this persist after deselection
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
Thanks in advance!
You could animate the Foreground property:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(ListBoxItem.Foreground).(SolidColorBrush.Color)"
To="Black" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The downside of this simple approach is that the information is not stored somewhere. This is pure visualization without any data backing. In order to persist the information, so that restarting the application shows the same previous state, you should introduce a dedicated property to your data model e.g IsMarkedAsRead.
Depending on your requirements, you can override the ListBoxItem.Template and bind ToggleButton.IsChecked to IsMarkedAsRead or use a Button which uses a ICommand to set the IsMarkedAsRead property. There are many solutions e.g. implementing an Attached Behavior.
The following examples overrides the ListBoxItem.Template to turn the ListBoxItem into a Button. Now when the item is clicked the IsMarkedAsRead property of the data model is set to true:
Data model
(See Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern for an implementation example of the RelayCommand.)
public class Notification : INotifyPropertyChanged
{
public string Text { get; set; }
public ICommand MarkAsReadCommand => new RelayCommand(() => this.IsMarkedAsRead = true);
public ICommand MarkAsUnreadCommand => new RelayCommand(() => this.IsMarkedAsRead = false);
private bool isMarkedAsRead;
public bool IsMarkedAsRead
{
get => this.isMarkedAsRead;
set
{
this.isMarkedAsRead = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ListBox
<ListBox ItemsSource="{Binding Notifications}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="{TemplateBinding Background}">
<Button x:Name="ContentPresenter"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=ItemTemplate}"
Content="{TemplateBinding Content}"
Command="{Binding MarkAsReadCommand}"
Foreground="Red">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border>
<ContentPresenter />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsMarkedAsRead}" Value="True">
<Setter TargetName="ContentPresenter" Property="Foreground" Value="Green" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Notification}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thanks a lot #BionicCode for the comprehensive answer. I ended up going with another solution which may or may not be good convention; I am a hobbyist.
Firstly, I don't need databacking / persistence.
Concerning the data model solution and overriding ListBoxItem.Template, I am using a prededfined class 'SyndicationItem' as the data class (my app is Rss Reader). To implement your datamodel solution I guess I could hack an unused SyndicationItem property, or use SyndicationItem inheritance for a custom class (I'm guessing this is the most professional way?)
My complete data model is as follows:
ObservableCollection >>> CollectionViewSource >>> ListBox.
Anyway I ended up using some simple code behind which wasn't so simple at the time:
First the XAML:
<Window.Resources>
<CollectionViewSource x:Key="fooCollectionViewSource" Source="{Binding fooObservableCollection}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="PublishDate" Direction="Descending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style x:Key="DeselectedTemplate" TargetType="{x:Type ListBoxItem}">
<Setter Property="Foreground" Value="Gray" />
</Style>
</Window.Resources>
<ListBox x:Name="LB1" ItemsSource="{Binding Source={StaticResource fooCollectionViewSource}}" HorizontalContentAlignment="Stretch" Margin="0,0,0,121" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<TextBlock MouseDown="TextBlock_MouseDown" Grid.Column="0" Text="{Binding Path=Title.Text}" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Column="1" HorizontalAlignment="Right" TextAlignment="Center" FontSize="11" FontWeight="SemiBold"
Text="{Binding Path=PublishDate.LocalDateTime, StringFormat='{}{0:d MMM, HH:mm}'}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now the code behind:
Solution 1: this applies a new style when listboxitem is deselected. Not used anymore so the LB1_SelectionChanged event is not present in the XAML.
private void LB1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count != 0)
{
foreach (var lbItem in e.RemovedItems)
{
//get reference to source listbox item. This was a pain.
int intDeselectedItem = LB1.Items.IndexOf(lbItem);
ListBoxItem lbi = (ListBoxItem)LB1.ItemContainerGenerator.ContainerFromIndex(intDeselectedItem);
/*apply style. Initially, instead of applying a style, I used mylistboxitem.Foreground = Brushes.Gray to set the text color.
Howver I noticed that if I scrolled the ListBox to the bottom, the text color would revert to the XAML default style in my XAML.
I assume this is because of refreshes / redraws (whichever the correct term). Applying a new style resolved.*/
Style style = this.FindResource("DeselectedTemplate") as Style;
lbi.Style = style;
}
}
}
Solution 2: The one I went with. Occurs on SelectedItem = true, same effect as your first suggestion.
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBlock tb = e.Source as TextBlock;
tb.Foreground = Brushes.Gray;
}
I'm just starting to learn UWP and xaml. What is the proper way to add a AutoSuggestBox to the Side Navigation panel? (Sorry for the bad code formatting in advance, copy and paste wasn't great)
My Main.xaml has an AutoSuggestArea that I have set to Visible
</VisualStateGroup>
<VisualStateGroup x:Name="AutoSuggestGroup">
<VisualState x:Name="AutoSuggestBoxVisible"/>
<VisualState x:Name="AutoSuggestBoxCollapsed">
<VisualState.Setters>
<Setter Target="AutoSuggestArea.Visibility" Value="Visible"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
and in the Grid for the AutoSuggestArea I have defined an AutoSuggestBox
<Grid x:Name="AutoSuggestArea" Height="44" Grid.Row="3" VerticalAlignment="Center">
<ContentControl x:Name="PaneAutoSuggestBoxPresenter" Content="{TemplateBinding AutoSuggestBox}" HorizontalContentAlignment="Stretch" IsTabStop="False" Margin="16,0,16,0" VerticalContentAlignment="Center"/>
<Button x:Name="PaneAutoSuggestButton" Content="" MinHeight="44" Style="{TemplateBinding PaneToggleButtonStyle}" Visibility="Collapsed" Width="{TemplateBinding CompactPaneLength}"/>
<AutoSuggestBox Width="234" VerticalAlignment="Center"
HorizontalAlignment="Center"
PlaceholderText="Search" Name ="boxS"
QuerySubmitted="AutoSuggestBox_QuerySubmitted"
TextChanged="AutoSuggestBox_TextChanged">
<AutoSuggestBox.TextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="IsHandwritingViewEnabled" Value="False"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</AutoSuggestBox.TextBoxStyle>
<AutoSuggestBox.QueryIcon>
<SymbolIcon Symbol="Find" Foreground="Black">
<SymbolIcon.RenderTransform>
<CompositeTransform ScaleX="1" ScaleY="1"/>
</SymbolIcon.RenderTransform>
</SymbolIcon>
</AutoSuggestBox.QueryIcon>
</AutoSuggestBox>
</Grid>
What I want is basically Identical Behaviour as the Groove Music app on Windows, where the Search bar disappears as the Nav View is closed or Minimized.
Instead I get this
I am assuming you meant NavigationView by NavigationPanel.
This is not how you put an AutoSuggestBox in NavigationView. NavigationView has an NavigationView.AutoSuggestBox property. You just set an AutoSuggestBox on this property, and every thing will work as expected. Like this:
<NavigationView>
<NavigationView.AutoSuggestBox>
<AutoSuggestBox x:Name="NavViewSearchBox" QueryIcon="Find"/>
</NavigationView.AutoSuggestBox>
</NavigationVew>
You don't have to hide/show this AutoSuggestBox yourself. NavigationView will automatically do this for you. Also, you don't have to put thie AutoSuggestBox inside any grid or anything.
I saw some examples for changing colors and brushes for selected item in a ListBox
I was wondering if there is a way to change visual properties of an item in a list box based on events in our code
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="spSceneThumb" Width="110">
<Border BorderThickness="1" Background="#FFfcfcfc" BorderBrush="#aaaaff" >
<StackPanel></StackPanel>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Let say I want to change border color of 5th item based on some event
I tried IValueConverter But changes to property wont effect border color
You can declare a custom RoutedEvent that you could listen to with an EventTrigger. You can find out how to declare a custom RoutedEvent in the How to: Create a Custom Routed Event page on MSDN. Once created, you can reference your custom event using the class name before the event name and not forgetting the XAML Namespace Prefix that you define for the namespace where it was declared. Something like this:
RoutedEvent="YourNamespacePrefix:YourClassName.YourEventName"
However, changing discrete values like Brushes is not so simple using an EventTrigger. You'll have to use a Storyboard with a DiscreteObjectKeyFrame element. You could try something like this:
<DataTemplate x:Key="Something">
<StackPanel Name="spSceneThumb" Width="110">
<Border Name="Border" BorderThickness="1" Background="#FFFCFCFC">
<StackPanel>
...
</StackPanel>
<Border.Resources>
<SolidColorBrush x:Key="EventBrush" Color="Red" />
</Border.Resources>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="#FFAAAAFF" />
<Style.Triggers>
<EventTrigger RoutedEvent="Prefix:YourClassName.YourEventName">
<EventTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="Border"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{StaticResource EventBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.EnterActions>
</EventTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</StackPanel>
</DataTemplate>
So I am trying to create a simple animation that takes the background from the initial color to the new color and back.
The original issue I had is that I created a trigger on the MouseDownEvent that triggered the animation, but the user could trigger another animation before the first one finished. This new animation would animate from the current shade it was at to the new color and back. By progressively restarting the animation while the animation is going, the original color is lost.
The easiest way to solve this is probably if i use the completed event for the animation. However, I don't like this solution because I want my animations to be in a custom style in resource dictionary and not part of the control itself. If the animation is in a custom style in a resource dictionary, then it won't have access to the code behind of the control itself. Is there a good way to get an animation working while maintaining separation between the style and the control?
I then had a different idea. The error is caused because I was animating from the border.background.color to a new color and back, thus if i started a new animation while the old one was going, the new animation started from whatever color value the prior animation was in. But if I set the animation to go back to some saved property value of the original background color then I won't have the issue even if the user restarts the animation.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Components="clr-namespace:DaedalusGraphViewer.Components"
xmlns:Converters="clr-namespace:DaedalusGraphViewer.Components.Converters"
>
<Converters:ThicknessToLeftThicknessConverter x:Key="ThicknessToLeftThicknessConverter" />
<Style x:Key="SearchBoxListViewItemStyle" TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Type Components:SearchBox}" TargetType="{x:Type Components:SearchBox}">
<Style.Resources>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Components:SearchBox}">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="LayoutGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer
x:Name="PART_ContentHost"
Grid.Column="0"
VerticalAlignment="Center"
/>
<Label
x:Name="DefaultTextLabel"
Grid.Column="0"
Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TextColor}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelText}"
VerticalAlignment="Center"
FontStyle="Italic"
/>
<Popup x:Name="RecentSearchesPopup"
IsOpen="False"
>
<ListView
x:Name="PreviousSearchesListView"
ListView.ItemContainerStyle="{StaticResource SearchBoxListViewItemStyle}"
>
</ListView>
</Popup>
<Border
x:Name="PreviousSearchesBorder"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="LightGray"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness,
Converter={StaticResource ThicknessToLeftThicknessConverter}}"
>
<Image
x:Name="PreviousSearchesIcon"
ToolTip="Previous Searches"
Width="15"
Height="15"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="pack://application:,,,/DaedalusGraphViewer;component/Images/Previous.png"
/>
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasText" Value="True">
<Setter Property="Visibility" TargetName="DefaultTextLabel" Value="Hidden" />
</Trigger>
<Trigger
SourceName="DefaultTextLabel"
Property="IsMouseOver"
Value="True"
>
<Setter Property="Cursor" Value="IBeam" />
</Trigger>
<!--<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="PreviousSearchesBorder">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
AutoReverse="True"
Duration="0:0:0.2"
Storyboard.TargetName="PreviousSearchesBorder"
Storyboard.TargetProperty="(Border.Background).Color"
To="Black"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>-->
<Trigger Property="IsPopupOpening" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="PreviousSearchesBorder"
Storyboard.TargetProperty="(Border.Background).Color"
>
<LinearColorKeyFrame KeyTime="0:0:0.0" Value="{x:Static Components:SearchBox.DefaultRecentSearchesButtonColor}" />
<LinearColorKeyFrame KeyTime="0:0:0.2" Value="Black" />
<LinearColorKeyFrame KeyTime="0:0:0.4" Value="{x:Static Components:SearchBox.DefaultRecentSearchesButtonColor}" />
</ColorAnimationUsingKeyFrames>
<!--<ColorAnimation
AutoReverse="True"
Duration="0:0:0.2"
Storyboard.TargetName="PreviousSearchesBorder"
Storyboard.TargetProperty="(Border.Background).Color"
To="Black"
/>-->
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
However, in order to do this I need to store the original background property and I have yet to get that working. I cannot using binding because properties in Animations must be freezeable, so I tried creating a static property on the control that gets set to the original value on the control's loaded event.
I set the color to the background color in the code behind, but the style does not reflect that property.
Is my static reference in the xaml correct? if so, then isn't onapplytemplate when the style should load the color from the static reference?
Well, you can't use a DynamicResource without getting a Freeze error.
What if you loaded the Xaml file at runtime and added the StaticResource to the Xaml file?
Here is an example. (I use some quick and dirty parsing of the Xaml file, but it works for the proof of concept.)
Change the properties of the StoryBoardResourceDictionary.xaml to Content and Copy if newer and remove the MSBuild:Compile setting.
StoryBoardResourceDictionary.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="WindowWithTrigger" TargetType="Window">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseDown">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" >
<EasingColorKeyFrame KeyTime="0:0:2" Value="White"/>
<!-- OriginalBackground is added at runtime -->
<EasingColorKeyFrame KeyTime="0:0:4" Value="{StaticResource OriginalBackground}"/> <!-- Load at runtime -->
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BackgroundAnimationBlend.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480"
Style="{DynamicResource WindowWithTrigger}"
Background="DarkBlue"> <!--Change background to whatever color you want -->
</Window>
MainWindow.xaml.cs
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Markup;
namespace BackgroundAnimationBlend
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var rd2 = LoadFromFile();
this.Resources.MergedDictionaries.Add(rd2);
}
public ResourceDictionary LoadFromFile()
{
const string file = "Styles/StoryBoardResourceDictionary.xaml";
if (!File.Exists(file))
return null;
using (var fs = new StreamReader(file))
{
string xaml = string.Empty;
string line;
bool replaced = false;
while ((line = fs.ReadLine()) != null)
{
if (!replaced)
{
if (line.Contains("OriginalBackground"))
{
xaml += string.Format("<Color x:Key=\"OriginalBackground\">{0}</Color>", Background);
replaced = true;
continue;
}
}
xaml += line;
}
// Read in an EnhancedResourceDictionary File or preferably an GlobalizationResourceDictionary file
return XamlReader.Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml))) as ResourceDictionary;
}
}
}
}
I have no idea how to scale this right now. So maybe it is a crazy idea. But loading the style at runtime and injecting the current background as a xaml string into the style before loading it is the only idea I had that worked.
I have made a small example to illustrate how you can solve your problem (by what i have understood).
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Color x:Key="background">yellow</Color>
<SolidColorBrush x:Key="backgroundBrush" Color="{StaticResource background}"/>
<Storyboard x:Key="Storyboard1">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="grid">
<SplineColorKeyFrame KeyTime="0:0:0.3" Value="Black"/>
<SplineColorKeyFrame KeyTime="0:0:0.6" Value="{StaticResource background}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid x:Name="grid" Background="{StaticResource backgroundBrush}">
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.5,0.5">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource Storyboard1}" Name="Storyboard1"/>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
I have defined a Color background as StaticResource to use it as Background-Color for the Grid and as Value for the Animation.
In the example above the Grid has a yellow background, that gets animated to black and then back to yellow when the button is clicked.
So all I ended up doing was using FillBehavior = Stop. I was new to animations so I didn't know how to prevent the reactivation of the animation easily without access to the completed event (which would have to be attached in the control, thus defeating the purpose of having a style with animations in a separate resource file). Regardless, I'm giving the bounty to rhyous because his answer actually does solve my problem as well, though in a more difficult manner. I knew that what i wanted to do had to be simple because there was no way that no one had wanted to animate a color and back from a style, yet I couldn't find any Stack Overflow questions on the issue
I have a custom Expander control called SpecialExpander. It is basically just a standard Expander with a fancy header and a couple properties (HeaderText and IsMarkedRead).
I began by creating a simple class:
public class SpecialExpander : Expander
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
Then I created a style that sets a couple properties on the expander (e.g., margins, padding, etc.) and, importantly, it also defines a custom DataTemplate for the HeaderTemplate property. The template is basically a grid with two rows.
As shown in the illustrations below...
for the top row, I'd like a fixed layout (it's always TextBlock TextBlock CheckBox)
for the bottom row, however, I want to be able to provide custom XAML for each expander.
I tried putting <ContentControl Grid.Row="1" ... /> in the DataTemplate, but I couldn't figure out how to hook it up properly.
alt text http://img85.imageshack.us/img85/1194/contentcontrolwithintem.jpg
Question
How can I build a DataTemplate for my SpecialExpander so that the header has some fixed content (top row) and a place-holder for custom content (bottom row)?
For the second illustration, I would want to be able to do something like this:
<SpecialExpander HeaderText="<Expander Header Text>" IsMarkedRead="True">
<SpecialExpander.Header>
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Med" />
<RadioButton Content="Low" />
</StackPanel>
<SpecialExpander.Header>
<Grid>
<Label>Main Content Goes Here</Label>
</Grid>
</SpecialExpander>
It hit me this morning how to solve this: instead of building a SpecialExpander, I just need a normal Expander. Then, for the header, I will use a custom ContentControl called SpecialExpanderHeader.
Here's how it works...
SpecialExpanderHeader class:
public class SpecialExpanderHeader : ContentControl
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
SpecialExpanderHeader style:
<Style TargetType="custom:SpecialExpanderHeader">
<Setter Property="Padding" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="custom:SpecialExpanderHeader">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=HeaderText}" />
<CheckBox Margin="100,0,0,0" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=IsMarkedRead}" />
</StackPanel>
<Separator Grid.Row="1" />
<ContentPresenter Grid.Row="2" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Expander style
<Style x:Key="Local_ExpanderStyle" TargetType="Expander" BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="Margin" Value="0,0,0,10" />
<Setter Property="Padding" Value="10" />
<Setter Property="FontSize" Value="12" />
</Style>
Usage
<Expander Style="{StaticResource Local_ExpanderStyle}">
<Expander.Header>
<custom:SpecialExpanderHeader IsMarkedRead="True" HeaderText="Test">
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Medium" />
<RadioButton Content="Low" />
</StackPanel>
</custom:SpecialExpanderHeader>
</Expander.Header>
<Grid>
<!-- main expander content goes here -->
</Grid>
</Expander>