Different XAML Layout Structure for one CS file in UWP - c#

We need to create 4 different .xaml layouts for same .cs in our UWP application. Structure of those layouts depends on the value "questionType" which comes from db.
Each variation of layout should contain same controls but in different positions.(i.e each variation should contain one image, one richTextEditor and radioGroup that consist 4 radios)
For instance if questionType=1, image should be positioned on the left side of the screen, if questionType=2, then it should be positioned on the top of rich text editor and also radios should be positioned horizontally...
Things that we have already considered and tried:
Until now, we have tried visual state manager, but unfortunately by using it we could only change the alignment not positions of controls.
We also checked conditional xaml but it seems it is only for version adaptability.
Panels with changing visibility. But we decided not to go with this solution because it is very ugly.
Anyone who will direct us to the right direction, will be appreciated.
Thanks.
Edit:
<VisualState x:Name="Layout1">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.HorizontalAlignment)"
Storyboard.TargetName="ContentRoot">
...
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>

VisualStateManager can change whatever property you define on an element, not just Alignments.
For the sake of simplicity, I use two Borders to represent the Image and the RichTextBox. Initially the Image is positioned to the left, and then I use VisualStateManager to go to another visual state, in which the Image is positioned to the top. Note that the properties (Grid.Column) and (Grid.Row) are changed just like (FrameworkElement.HorizontalAlignment)
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border x:Name="imageControl"
Background="Red"
Height="200" Width="200"
Grid.Row="1" />
<Border x:Name="richTextBoxControl"
Background="Green"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.Row="1" Grid.Column="1" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Layout1" />
<VisualState x:Name="Layout2">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="imageControl"
Storyboard.TargetProperty="(Grid.Column)">
<DiscreteObjectKeyFrame KeyTime="0" Value="1" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="imageControl"
Storyboard.TargetProperty="(Grid.Row)">
<DiscreteObjectKeyFrame KeyTime="0" Value="0" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
And in code behind, change the VisualState based on the value questionType.
if (questionType == 1)
return; //Layout1 is the default state
else if (questionType == 2)
VisualStateManager.GoToState(this, "Layout2", true);
There are alternative ways, like using a StackPanel to host the controls, initially horizontally oriented, and change it to vertically oriented in the other visual state.

Related

Acrobat-like PopUp in WPF

Im recently working on some simple Imageviewer.
Now it came to my mind, it might be a nice feature, to do some context-sensitve actions like Zooming and rotating.
To implement these functions is not my problem, but the ContextMenu is.
I've decided to not use a ContextMenu-Element, instead im going to use a popup.
Reasons for PopUp:
Less Styling
Better Positioning
IsOpen is Bindable (ContextMenu is NOT bindable on IsOpen against all Articles regarding this)
Here comes the trouble:
<Image x:Name="PART_ImgCurrent" VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="Uniform" Grid.Row="0" Grid.Column="0"
Source="{Binding ElementName=PART_PreviewPanel, Path=SelectedItem.Source}">
<Image.LayoutTransform>
<RotateTransform Angle="0"></RotateTransform>
</Image.LayoutTransform>
<Image.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PART_ImgCurrent" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
<Popup IsHitTestVisible="False" Focusable="False" PlacementTarget="{Binding ElementName=PART_ImgCurrent}" AllowsTransparency="True" StaysOpen="True"
IsOpen="{Binding ElementName=PART_ImgCurrent, Path=IsMouseOver, Mode=OneWay}"
Placement="Right" HorizontalOffset="-42" VerticalOffset="2">
<StackPanel Opacity="0.5" VerticalAlignment="Stretch">
<Button Content="Ugly Button" Height="40" Width="40"></Button>
<Button Content="Ugly Button" Height="40" Width="40"></Button>
<Button Content="Ugly Button" Height="40" Width="40"></Button>
</StackPanel>
</Popup>
As you can see, im binding IsOpen of Popup to IsMouseOver on Image which results in a funny Disco-BlinkenLights-Behavior when i try to click a button inside the Popup.
What has this to do with the Title?
AcrobatReader has this
This is almost exactly the behavior im looking for. How is this thing called?
Or had someone ever similar issues and could provide a solution?
Sorry for the delay, soon as I thought I had a second I got busy again. Anyway, here's one of several ways I can think of accomplishing your goal, give it a shot. I sort of assumed it may not be just images you want this for and if you threw the resource stuff in a dictionary and kept your naming consistent (or even better, just target the nested UIElement) you could use it all over the place. Notice the Grid is acting as what would be the Image in this example.
I generally make things open for future added interactions and stuff, so in this case I would probably just make the image source the background brush for Grid or place it as a child. That way if you decide to add other objects in there or say other effects and stuff you've got a good start point.
Anyway I digress, so try out the concept example below and see if it's what you're after. If not, like I said there's several other ways I can think of to accomplish your goal so just let me know. :)
<!-- HitTestVisibility Area -->
<Grid x:Name="ImagePlaceholder"
Height="500" Width="500"
Background="LightBlue">
<Grid.Resources>
<Storyboard x:Key="OnMouseEnter">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
Storyboard.TargetName="FakePopUp">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnMouseLeave">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
Storyboard.TargetName="FakePopUp">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
<Grid.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter">
<BeginStoryboard Storyboard="{StaticResource OnMouseEnter}"/>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource OnMouseLeave}"/>
</EventTrigger>
</Grid.Triggers>
<!-- Overlay -->
<Border Name="FakePopUp" Visibility="Collapsed"
Margin="0,0,0,25" Background="SlateGray"
Height="50" CornerRadius="20" Padding="10"
HorizontalAlignment="Center" VerticalAlignment="Bottom">
<StackPanel Orientation="Horizontal">
<Button Content="Eins Bier"/>
<Button Content="Zwei Bier" Margin="10,0"/>
<Button Content="Drei Bier"/>
</StackPanel>
</Border>
</Grid>
I went with Storyboards attached to the parent instead of direct triggers with TargetName like I said, because I could think of a bunch of other instances features might want to be added that would make sense. Even something simple like adding a transition duration for a nice fade effect or maybe a translate y to slide it up while fading etc, etc, etc.
Anyway, hope this helps. Cheers!

Animation limited by panel

It's a little hard to describe but I'll try my best.
I have a control which has an image and a label and it needs to have 2 states ("Big", and "Small").
On the "Big" state the image should be centered at the top of the control, and the label should be center below (Just like a dock with an image and a label docked to the top).
On the "Small" state the image should be smaller and at the top left of the control, and the label should be right next to it.
The big state should look like so:
And the small state:
And the tricky part: when I switch between them I need it to animate over 0.3s.
There is no panel I found suitable for this.
DockPanel is a good solution for both of these states, but it can't animate it.
Canvas can animate it, but doesn't have a proper layout (can't center them so easily).
What would be the best way to do it?
In WPF no animation alignment, the only thing that can come up - it ThicknessAnimation. But you can use the DiscreteObjectKeyFrame to set the alignment. Below is a simple demonstration in which to Label set VerticalAlignment in Bottom:
<Grid>
<Grid.Triggers>
<EventTrigger SourceName="Small" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Test" Storyboard.TargetProperty="VerticalAlignment">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<VerticalAlignment>Bottom</VerticalAlignment>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Label x:Name="Test" Content="Test" Width="300" Height="300" Background="Aqua" VerticalAlignment="Top" HorizontalAlignment="Center" />
<Button Name="Small" Content="Small" Width="100" Height="30" HorizontalAlignment="Right" VerticalAlignment="Top" />
</Grid>
Using it in combination with standard animations, such as DoubleAnimation, I think you'll be able to achieve this goal.

Adding a Top Navigation Bar in Windows 8 - Tutorial Issue

I am trying out a tutorial on WIndows 8. I need to add a Navigation Bar. The steps are given below;
The following is from the tutorial.
In Solution Explorer, double-click MainPage.xaml to open it.
In the Document Outline, select the "pageRoot" element.
In the Properties panel, click the Properties button () to show the Properties view.
Under Common in the Properties panel, find the TopAppBar property.
Click the New button next to TopAppBar. An AppBar control is added to the page.
In the Document Outline, expand the TopAppBar property.
Select the "photoPageButton" element, drag it onto the AppBar, and drop it.
Under Layout in the Properties panel, set the HorizontalAlignment property to Right ().
Press F5 to build and run the app. To test the app bar, right-click on the main page. The app bar opens at the top of the screen.
I Double-clicked on the MainPage.xaml, and then in the Document Outline i selected pageRoot. and in the properties Window expanded Common and i clicked on New next to TopAppBar.
It added several other Fields below it. Allow Drop, Background and Cache Mode are some of it. Then in the Document Outline i dragged the button to the AppBar under TopAddBar. Changed the HorizontalAlignment to Right, build and executed the application. But i don't see the button added to the top Navigation bar. What have i done wrong here ?
UPDATE
<Page.Resources>
<!-- TODO: Delete this line if the key AppName is declared in App.xaml -->
<x:String x:Key="AppName">My Application</x:String>
</Page.Resources>
<common:LayoutAwarePage.TopAppBar>
<AppBar Background="#E5A41D1D" AllowDrop="True" BorderBrush="#E5C5A7A7" HorizontalAlignment="Right">
<Button Content="Next Page
" HorizontalAlignment="Right" VerticalAlignment="Stretch" Width="230" Height="70" Background="#FF12668D" FontFamily="Shruti" FontSize="36" Click="Button_Click_2"/>
</AppBar>
</common:LayoutAwarePage.TopAppBar>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}" Background="#FF282D40">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Back button and page title -->
<!-- Back button and page title -->
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
<TextBlock x:Name="pageTitle" Grid.Column="1" Text="Welcome !!! " Style="{StaticResource PageHeaderTextStyle}" Foreground="#DE2374AC"/>
</Grid>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- The back button and title have different styles when snapped -->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
You're following this tutorial here, correct? It looks like you've dragged the wrong button to your TopAppBar.
The button you should be dragging is named photoPageButton (its x:Name attribute). Instead, the button you've got in your TopAppBar doesn't have a name, and presents the text "Next Page".
Your XAML markup for the TopAppBar should look something like this after you've dragged the photoPageButton to the TopAppBar:
<common:LayoutAwarePage.TopAppBar>
<AppBar HorizontalAlignment="Right">
<Button x:Name="photoPageButton" Content="Go to photo page"/>
</AppBar>
</common:LayoutAwarePage.TopAppBar>
After you get a little farther in the tutorial and apply a style to the button, your markup for the TopAppBar will then look like this:
<common:LayoutAwarePage.TopAppBar>
<AppBar HorizontalAlignment="Right">
<Button x:Name="photoPageButton"
Click="photoPageButton_Click"
HorizontalAlignment="Right"
Style="{StaticResource PicturesAppBarButtonStyle}"/>
</AppBar>
</common:LayoutAwarePage.TopAppBar>
It's perfectly acceptable to have the other AppBar settings in there as well - Background, BorderBrush; these are harmless changes to the color - and AllowDrop defaults to true, I believe, so that's fine too.

Modified Data Binding

I'm creating a transition effect that will slide 2 panels (side to side).
My plan is to have a Grid that has the width of the window multiplied by 2 through data-binding, divide it by 2 to have the 2 panels, then put Grids in them (so I'll have 2 panels equally divided with their own grid and you can see only one of them at a time. all it takes now is to move the root grid with an animation to create the transition effect).
So I'm trying to data bind the Grid's width to the width of the window multiplied by 2 (as I've written in bold above), but I have no idea how you can modify a data bind, and let it keep updating accordingly. so if possible - how do you do it?
I guess I could do it with events (just like in any other .NET application) but that's just more opportunities for bugs to come and fester (i.e. events I'll miss); so I'd really appreciate a good and clean solution.
By the way, if you have a better way to make the transition, please let me know in the comments. My mind is still open to ideas.
Thanks!
I've done it. It was like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="0" />
<Grid x:Name="Panel1" />
<Grid x:Name="Panel2"/>
</Grid>
Now, this is side-scrolling, however the point is same. All you do is apply RenderTransform.TranslateTransform animation to Panel2, you animate X from 0 to Panel2 Width, essentially it will be completely off, and after that you just set Panel2 Column to "1".
I can give you tomorrow little code example too, but you can get started.
Here is the Xaml that takes care of slide2slide animation when user toggles the button.
<Grid.Triggers>
<EventTrigger RoutedEvent="ToggleButton.Checked" SourceName="workingPlanButton">
<BeginStoryboard>
<Storyboard Completed="Storyboard_Completed">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:0.1"
Duration="0:0:00.5"
Storyboard.TargetName="lsView"
Storyboard.TargetProperty="RenderTransform.(TranslateTransform.Y)"
Timeline.DesiredFrameRate="30">
<LinearDoubleKeyFrame KeyTime="00:00:00.3" Value="{Binding ElementName=bodyGrid, Path=ActualHeight}" />
</DoubleAnimationUsingKeyFrames>
<!-- Hide the panel -->
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0.5"
Storyboard.TargetName="lsView"
Storyboard.TargetProperty="Visibility"
Timeline.DesiredFrameRate="30">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Now the actual grid:
<Grid x:Name="bodyGrid" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="1" />
</Grid.RowDefinitions>
<StudyPlan:StudyPlanControl />
<local:LessonControl x:Name="lsView />
<local:LessonControl.RenderTransform>
<TranslateTransform Y="0" />
</local:LessonControl.RenderTransform>
</local:LessonControl>
</Grid>

Separate event in control

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.

Categories