I'm making a zoom control (Slider) with a TextBlock indicator that tells you want the current scale factor is (kinda like in the bottom-right corner of Word).
I'm a couple days into learning WPF, and I've been able to figure out how to do most of it, but I get the sense there's a much simpler way (one which, perhaps, only involves XAML-side code rather than a bunch of mouse events being captures.
I'd like for a the text to be underlined when hovered over (to imply clickability) and for a click to reset the slider element to 1.0.
Here's what I have:
<StatusBarItem Grid.Column="1" HorizontalAlignment="Right">
<Slider x:Name="mapCanvasScaleSlider" Width="150" Value="1" Orientation="Horizontal" HorizontalAlignment="Left"
IsSnapToTickEnabled="True" Minimum="0.25" Maximum="4" TickPlacement="BottomRight"
Ticks="0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4"/>
</StatusBarItem>
<StatusBarItem Grid.Column="2">
<TextBlock Name="zoomIndicator" Text="{Binding ElementName=mapCanvasScaleSlider,Path=Value,StringFormat=0%}"
MouseDown="ResetZoomWindow" MouseEnter="zoomIndicator_MouseEnter" MouseLeave="zoomIndicator_MouseLeave"
ToolTip="Zoom level; click to reset"/>
</StatusBarItem>
private void ResetZoomWindow(object sender, MouseButtonEventArgs args)
{
mapCanvasScaleSlider.Value = 1.0;
}
private void zoomIndicator_MouseLeave(object sender, MouseEventArgs e)
{
zoomIndicator.TextDecorations = TextDecorations.Underline;
}
private void zoomIndicator_MouseLeave(object sender, MouseEventArgs e)
{
zoomIndicator.TextDecorations = null;
}
I feel as though there's a better way to do this through XAML rather than to have three separate .cs-side functions.
You could use a style trigger for the text block, like described in this other post How to set MouseOver event/trigger for border in XAML?
Working solution:
<StatusBarItem Grid.Column="2">
<TextBlock Name="zoomIndicator" Text="{Binding ElementName=mapCanvasScaleSlider,Path=Value,StringFormat=0%}"
MouseDown="ResetZoomWindow" ToolTip="Zoom level; click to reset">
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.TextDecorations" Value="" />
<Style.Triggers>
<Trigger Property="TextBlock.IsMouseOver" Value="True">
<Setter Property="TextBlock.TextDecorations" Value="Underline" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StatusBarItem>
Still have to reset the zoom level manually (I think), though.
You can use VisualState (if you're using Blend its easy to edit).
Personally I prefer style triggers, unless I have to add StoryBoard animation - then I offen use VisualState
about VisualState
Typically, you wouldn't want to use a TextBlock as a button (although of course you can). You may want to consider using a more appropriate control like Button or HyperlinkButton, which have the basic mouse event handling already wired up. You can then apply whatever styles you like. A Button control, for example, can be easily styled re-templated as a TextBlock with underline on mouse-over:
<Style TargetType="Button" x:Key="LinkButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard Duration="0:0:0.1">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="content" Storyboard.TargetProperty="TextDecorations">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<TextDecorationCollection>Underline</TextDecorationCollection>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="content" Text="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Use it by referencing the style key:
<Button Content="click" Style="{StaticResource LinkButtonStyle}" />
Using this approach (rather than the alternative of adding triggers to a TextBlock) brings some advantages, which are built in to the Button control.
You can apply styles to compound states like Pressed
You can use the Click event, and its related Command property
Related
I am trying to use visual states to mark a TextBox input as invalid (by changing its border color to red) during input validation when a user submits a form the TextBox is a part of. I have the following code:
XAML
<Page.Resources>
<!-- Other resources omitted for brevity -->
<Flyout x:Key="NewTimeBlockFlyout">
<StackPanel>
<!-- Other stuff here omitted for brevity -->
<TextBox Margin="5"
Header="Name"
x:Name="NewTimeBlockNameTextBox">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Default"></VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="NewTimeBlockNameTextBox.Background" Value="Red"></Setter>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</TextBox>
<!-- The rest of the form omitted for brevity -->
<Button x:Name="CreateTimeBlockButton"
Grid.Column="0"
Margin="0,0,2,0"
HorizontalAlignment="Stretch"
Tapped="CreateTimeBlockButton_Tapped">Create</Button>
</StackPanel>
</Flyout>
</Page.Resources>
Note: This is on a XAML Page. I am NOT using a custom control.
C#
private void CreateTimeBlockButton_Tapped(object sender, TappedRoutedEventArgs e)
{
// Validate the input.
if (String.IsNullOrWhiteSpace(this.NewTimeBlockNameTextBox.Text))
{
// These two lines of code confirm the visual state named "Invalid" does exist on the textbox.
//List<VisualStateGroup> m = VisualStateManager.GetVisualStateGroups(this.NewTimeBlockNameTextBox).ToList();
//List<VisualState> c = m.FirstOrDefault().States.ToList();
// Assignment to bool just used to inspect the return value for debugging.
bool did = VisualStateManager.GoToState(this.NewTimeBlockNameTextBox, "Invalid", false);
}
}
Problem
No matter what I try, the call to VisualStateManager.GoToState() is always returning false.
Things I have tried:
Here is the relevant documentation from Microsoft.
As seen in the C# code above, I have verified the visual state "Invalid" does exist, as expected, on the "NewTimeBlockNameTextBox" control.
I have seen several solutions including here, and here that suggest moving the <VisualStateManager.VisualStateGroups> tag in the XAML to outside of the TextBox, or to the root of the Page. Neither have worked for me.
I have also seen these two solutions here and here, but neither seemed relevant to my situation, as both seem to have issues related to things I am not doing.
I can reproduce your issue. You need to place <VisualStateManager.VisualStateGroups> outside the TextBox and skip status through VisualStateManager.GoToState(this, "Invalid", false); Please refer to the following code.
Xaml code:
<Page
……
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Default"></VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="NewTimeBlockNameTextBox.BorderBrush" Value="Red"></Setter>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox Margin="5"
Header="Name"
x:Name="NewTimeBlockNameTextBox" >
</TextBox>
<Button x:Name="CreateTimeBlockButton"
Grid.Row="1"
Margin="0,0,2,0"
HorizontalAlignment="Stretch"
Tapped="CreateTimeBlockButton_Tapped">Create</Button>
</StackPanel>
</Page>
Code behind:
private void CreateTimeBlockButton_Tapped(object sender, TappedRoutedEventArgs e)
{
bool a = VisualStateManager.GoToState(this, "Invalid", false);
}
Updated:
For using VisualStateManager.GoToState(), which always be done by changing its controltemplate.
You could right click the textbox in XAML designer, then select the option Edit a Template->Edit a Copy, here you will see the default textbox style placed in <Page.Resources> </Page.Resources> tag.
<Style x:Key="TextBoxStyle1" TargetType="TextBox">
……
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
……
<VisualStateManager.VisualStateGroups>
……
<VisualState x:Name="TestState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="red"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="HeaderContentPresenter" …/>
<Border x:Name="BorderElement" …/>
……
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see, you could place custom state in its template, then you could use “VisualStateManager.GoToState(NewTimeBlockNameTextBox, "TestState", false);” to skip state.
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'm having a really hard time finding a way to do the following...
I have my "Image button style," and it is applied to every button in my Silverlight app. Is there any way to make a blanket VisualStateManager to change the image on MouseOver, that will work for every button? I have a couple ideas, but I'm not sure if they can be implemented...
Let's say my button image paths are consistent:
image1.png
image2.png
and
image1 - hover.png
image2 - hover.png
Is there a way to change the path of the image to append " - hover"? I thought about using an IValueConverter for this, but wasn't sure how to access the control state...
An alternative idea I had was if there was a way to accomplish this:
<Button>
<Image Source="../Images/image1.png" Width="70"/>
<Image Source="../Images/image1 - hover.png" Width="70"/>
</Button>
Have two contents (an array of content?) and on Normal state, set only the first content opacity to 1, and the rest to 0. Then on MouseOver, switch the opacities.
Are any of these solutions feasible, and how can they be implemented?
Thanks
Edit: I know I can make custom styles for each button that switch out their images, but I would like a blanket style that I can apply to all buttons.
You may (ab)use the Tag property for your purpose and create a ControlTemplate with two Image controls with a BitmapImage that binds its UriSource property to either the Content or the Tag property respectively:
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="image1"
Storyboard.TargetProperty="Opacity"
To="0" Duration="0:0:0.1"/>
<DoubleAnimation
Storyboard.TargetName="image2"
Storyboard.TargetProperty="Opacity"
To="1" Duration="0:0:0.1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Image x:Name="image1">
<Image.Source>
<BitmapImage UriSource="{Binding Content,
RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Image.Source>
</Image>
<Image x:Name="image2" Opacity="0">
<Image.Source>
<BitmapImage UriSource="{Binding Tag,
RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Image.Source>
</Image>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now you may declare a Button like this:
<Button Content="../Images/image1.png" Tag="../Images/image1_hover.png"/>
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>
I'm wondering if I can have a individual component in a control have an event. For example, I've created my own control, and with VisualStateManager I'm able to handle several events that fire for the control as a whole, I'd like just my togglebutton in the controlto fire the mouseover event. Currently any area of the control that the mouse is over fires the mouseover which ruins the coloranimation effect i'm trying to get, I want the coloranimation to only happen when the toggle button has a mouse over, my control has a contentpresent allowing for other content, i don't want that piece to fire the event.
Example of what i'm talking about below in generic.xaml file.
<Style TargetType="local:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyControl">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ViewStates">
<VisualState x:Name="Expanded">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ContentScaleTransform" Storyboard.TargetProperty="ScaleY" To="1" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ContentScaleTransform" Storyboard.TargetProperty="ScaleY" To="0" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<!-- This fires for any part of the control, is it possible to just create a mouseover for the ToggleButton below? -->
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" Content="{TemplateBinding HeaderContent}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5" Margin="1" x:Name="ExpandCollapseButton">
</ToggleButton>
</Grid>
<ContentPresenter Grid.Row="1" Margin="5" Content="{TemplateBinding Content}" x:Name="Content">
<ContentPresenter.RenderTransform>
<ScaleTransform x:Name="ContentScaleTransform"/>
</ContentPresenter.RenderTransform>
</ContentPresenter>
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Bob, You can access the togglebutton from your class like this:
ToggleButton myToggleButton = GetTemplateChild("toggleButton") as ToggleButton;
Create the events you want in your class. You may need to override the OnApplyTemplate method and create your event there, I'm still researching this part and don't fully understand it yet, you will have to play with it. I did override my OnApplyTemplate and it worked for me.
ToggleButton myToggleButton;
public override void OnApplyTemplate() {
base.OnApplyTemplate();
myToggleButton = GetTemplateChild("toggleButton") as ToggleButton;
myToggleButton.MouseEnter += new MouseEventHandler(myToggleButton_MouseEnter);
}
void myToggleButton_MouseEnter(object sender, MouseEventArgs e) {
VisualStateManager.GoToState(this, "MouseEnter", true);
}
Also make sure to setup your Template at the top:
[TemplateVisualState(Name = "MouseEnter", GroupName = "ViewStates")]
The VisualState can exist in the generic.xaml file where you are setting up your other visualstates and does not need to be in the inner ToggleButton.
<VisualState x:Name="MouseEnter">
<Storyboard>
<ColorAnimation Storyboard.TargetName="toggleButton" Storyboard.TargetProperty="(ToggleButton.SomeProperty).(SolidColorBrush.Color)" To="SomeColor"/>
</Storyboard>
</VisualState>
First of all lets get our terminology correct, you are not talking about "events" you are talking about "visual states". The "MouseOver" is a visual state. Its the responsibility of code within the control to decide which of the visual states the control is in.
Ordinarily code in the control will use the MouseEnter and MouseLeave events to set a boolean to indicate that the mouse is over the control and then call some UpdateStates method in which the developer will include logic to determine which visual states the control ought to be in currently.
In your case rather than using the controls MouseEnter and MouseLeave events attach these handlers to your inner ToggleButton instead.