Is it possible to remove or change default flyout transition animation? - xaml

When you add a flyout for a button control, for example, after you tap on that button the flyout is displayed with a transition animation which depends on the Placement property of the flyout. If the Placement is set to Top the animation looks like drop down from that button. In my case I would like to either remove the animation altogether or make it expanding by x and y from that button. How to do that?

There is a similar case:How to change ContentDialog transition.
As Rob Caplan said,You cannot override the transitions in the ContentDialog, etc. They are designed to be easy ways to get standard behaviour and will always use the PopupThemeTransition.
So if you want non-standard behaviour then you can write a custom control which uses your own TransitionCollection or without Transition.
For example:
<UserControl
x:Class="Is_it_possible_to_remove_or_change_default_flyou.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Is_it_possible_to_remove_or_change_default_flyou"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
LostFocus="UserControl_LostFocus">
<UserControl.Transitions>
<TransitionCollection>
<!--Add the Transition that you want-->
</TransitionCollection>
</UserControl.Transitions>
<Grid Name="MyGrid" BorderBrush="Gray" BorderThickness="1" Background="LightGray">
<StackPanel>
<TextBox Name="MyText" Text="Hello"></TextBox>
<TextBox Text="!"></TextBox>
</StackPanel>
</Grid>
</UserControl>
The code behind:
public sealed partial class MyUserControl1 : UserControl
{
public Popup hostPopup;
public double actHei;
public double actWei;
public MyUserControl1()
{
this.InitializeComponent();
hostPopup = new Popup();
hostPopup.Child = this;
}
public void Show()
{
hostPopup.IsOpen = true;
actHei = MyGrid.ActualHeight;
actWei = MyGrid.ActualWidth;
MyText.Focus(FocusState.Pointer);
}
private void UserControl_LostFocus(object sender, RoutedEventArgs e)
{
hostPopup.IsOpen = false;
}
}
In MainPage code behind:
public sealed partial class MainPage : Page
{
public MyUserControl1 popup;
public MainPage()
{
this.InitializeComponent();
popup = new MyUserControl1();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (popup.hostPopup.IsOpen != true)
{
var ttv = MyButton.TransformToVisual(Window.Current.Content);
Point screenCoords = ttv.TransformPoint(new Point(0, 0));
popup.hostPopup.VerticalOffset = screenCoords.Y - 100;
popup.hostPopup.HorizontalOffset = screenCoords.X;
popup.Show();
}
}
}

Contrary to what Rob Caplan and Jayden statet it is actually possible to override the default Transition of a ContentDialog.
You can create a class extending the ContentDialog and subscribe to to Loading event as shown in the code below.
There you can override the Transitions of the newly created popup for the ContentDialog.
I know this is a late answer and this might be a bit of a hacky solution but since I was looking for it I hope I can help somebody.
Example Code (no xaml):
public class CustomContentDialog : ContentDialog {
public CustomContentDialog() {
Loading += (sender, args) => {
// gets the background rectangle and the popup containing the ContentDialog
var popups = VisualTreeHelper.GetOpenPopups(Window.Current);
// remove all transitions and childtransitions from the popups
foreach (var p in popups) {
p.ChildTransitions = new TransitionCollection {
// your transitions
};
p.Transitions = new TransitionCollection {
// your transitions
};
}
};
}
}

Related

Stacklayout backgroundColor binding with MVVM

I'm attempting to get my head around MVVM with XamarinForms and I'm slightly confused with regards to proper partitioning of functionality:
I have a main page, MainPage.xaml, which includes a stacklayout:
<StackLayout x:Name="MainPageStackLayout">
...
</StackLayout>
Within this stacklayout I have Picker which is bound as follows:
<Picker Title="Select a background colour"
TitleColor="Black"
TextColor="Black"
ItemsSource="{Binding MyColours}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding selectedBackGroundColour}" SelectedIndexChanged="BackGroundColourPicker_SelectedIndexChanged"/>
Following the article from microsoft (https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/userinterface-monkeyapppicker/):
I have a "View" which basically defines the layout of my page.
A "ViewModel" which holds an IList "MyColours" and a variable "SelectedBackGroundColour".
A "Model" which defines the MyColour class. A MyColour has a string name and a Xamarin.Forms.Color (from a hex value, both populated on start up).
This all works fine. I can start up the app and the Picker populates with the colours I add to "MyColours". If I change the index then my SelectedBackGroundColour also updates, has the correct name and a different RGB value.
However, I'm lost as to where I would tie in the updating of the actual background colour of the MainPageStackLayout. The View (MainPage.xaml.cs) picks up the "BackGroundColourPicker_SelectedIndexChanged" event but what is the standard practice for reading from the view model (where SelectedBackGround colour is actual defined ?)
I have a feeling I can bind Background colour in the MainPageStackLayout xaml view so I wont have to catch the selected index change event.
Thanks all.
According to your description, I guess that you want to change MainPage StackLayout BackGround color by Picker value, am I right?
If yes, please follow the steps below.
Firstly, please confirm that you implement INotifyPropertyChanged interface to notify SelectedBackGroundColour changed.
Then there are full code, please take a look:
<StackLayout x:Name="MainPageStacklayout" BackgroundColor="{Binding selectedBackGroundColour.color}">
<Picker
x:Name="picker1"
Title="Select a background colour"
ItemDisplayBinding="{Binding name}"
ItemsSource="{Binding MyColours}"
SelectedItem="{Binding selectedBackGroundColour}"
TextColor="Black"
TitleColor="Black" />
</StackLayout>
public partial class Page5 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<MyColour> MyColours { get; set; }
private MyColour _selectedBackGroundColour;
public MyColour selectedBackGroundColour
{
get { return _selectedBackGroundColour; }
set
{
_selectedBackGroundColour = value;
RaisePropertyChanged("selectedBackGroundColour");
}
}
public Page5()
{
InitializeComponent();
MyColours = new ObservableCollection<MyColour>()
{
new MyColour(){name="red",color=Color.Red},
new MyColour(){name="gray",color=Color.Gray},
new MyColour(){name="BlueViolet",color=Color.BlueViolet}
};
selectedBackGroundColour = MyColours[0];
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MyColour
{
public string name { get; set; }
public Color color { get; set; }
}
The screenshot:

How to access code behind from Tablet/Phone template?

I am taking over a project with a DashboardPage and a DashboardPageViewModel that are linked through DI's ViewModelLocator. The Dashboard page have the following code that separates the Xaml/Code behinds into two separate templates.
private void SetContent()
{
Debug.WriteLine("Dashboardpage setContent");
switch(Device.Idiom)
{
case TargetIdiom.Phone:
Content = new PrimaryPhoneLayout
{
RegionContent = RegionContent
};
break;
case TargetIdiom.Tablet:
Content = new PrimaryTabletLayout(deviceDisplay)
{
RegionContent = RegionContent
};
break;
default:
throw new NotSupportedException($"{Device.Idiom} is not a supported idom");
}
}
I want to add a button in both Phone/Tablet xaml and handle the logic within DashboardPage. How can I make a reference to Dashboard page when these XAML files are linked to their individual code behind and not Dashboard Page?
To elaborate further, DashboardPage derives from MenuContainerPage that allows me to slide in/out of my slide menu. I want to handle this logic through a button that I implemented in both Tablet/Phone layout.
This is how I would do it.
I would start by creating an interface with the events I want to expose from my ContentView
For the sample, I will call this interface as IMenuOptionHandler and it would look like this
public interface IMenuOptionHandler
{
event EventHandler OnSlideIn;
event EventHandler OnSlideOut;
}
Here we have two events that will be invoked from our ContentViews. You can add as many as you wish.
Then we need to make our ContentViews to implement this interface:
public partial class PrimaryPhoneLayouts : ContentView, IMenuOptionHandler
{
//...
#region "IMenuOptionHandler implementation"
public event EventHandler OnSlideIn;
public event EventHandler OnSlideOut;
#endregion
void OnSlideInButtonClicked(object sender, EventArgs e)
{
OnSlideIn?.Invoke(this, EventArgs.Empty);
}
void OnSlideOutButtonClicked(object sender, EventArgs e)
{
OnSlideOut?.Invoke(this, EventArgs.Empty);
}
}
public partial class PrimaryTabletLayout : ContentView, IMenuOptionHandler
{
// ...
#region "IMenuOptionHandler implementation"
public event EventHandler OnSlideIn;
public event EventHandler OnSlideOut;
#endregion
void OnSlideInButtonClicked(object sender, EventArgs e)
{
OnSlideIn?.Invoke(this, EventArgs.Empty);
}
void OnSlideOutButtonClicked(object sender, EventArgs e)
{
OnSlideOut?.Invoke(this, EventArgs.Empty);
}
As you can see both classes are implementing our interface.
Also, I added two sets of methods which are the methods that you will hook to the Buttons on the XAML.
Let's imagine that your XAML looks like this:
<ContentView.Content>
<StackLayout Orientation="Vertical"
HorizontalOptions="FillAndExpand">
<Button Text="SlideIn"
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
Clicked="OnSlideInButtonClicked" />
<Button Text="SlideOut"
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
Clicked="OnSlideOutButtonClicked" />
</StackLayout>
</ContentView.Content>
Both XAML should have the buttons and the Clicked events wired to our methods in the Code behind classes.
These two methods the only purpose (as of now) is to invoke the events and notify anyone that it's subscribed to them that an event happened.
Now in your DashboardPage
you will add this global property for simplicity
IMenuOptionHandler MenuOptionHandler => Content as IMenuOptionHandler;
This will cast the Content of the Page, whatever it's the value, to IMenuOptionHandler. Any class that implements this interface will allow this cast to happen.
The last part to add on the same DashboardPage is the subscription to the events. These are gonna happen in the OnAppearing method and we will be unsubscribing on the OnDisappearing.
protected override void OnAppearing()
{
base.OnAppearing();
if (MenuOptionHandler != null)
{
MenuOptionHandler.OnSlideIn += MenuOptionHandler_OnSlideIn;
MenuOptionHandler.OnSlideOut += MenuOptionHandler_OnSlideOut;
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
if (MenuOptionHandler != null)
{
MenuOptionHandler.OnSlideIn -= MenuOptionHandler_OnSlideIn;
MenuOptionHandler.OnSlideOut -= MenuOptionHandler_OnSlideOut;
}
}
void MenuOptionHandler_OnSlideIn(object sender, EventArgs e)
{
//Logic to handle the SlideIn
Debug.WriteLine("MenuOptionHandler_OnSlideIn");
}
void MenuOptionHandler_OnSlideOut(object sender, EventArgs e)
{
//Logic to handle the SlideOut
Debug.WriteLine("MenuOptionHandler_OnSlideOut");
}
Now, whenever one of the Buttons on the ContentView (iPhone or Tablet) is clicked, the Dashboard ContentPage will be notified about this and you will be able to perform any task you wish.
Hope this helps.-
Assume you have a button in Page1, first give a name to the Button in Xaml:
<Button x:Name="btnInPage1" Text="Welcome to Xamarin.Forms!" />
In the code behind of Page1, create a public static property of button, and set the btnPageOne = btnInPage1:
public partial class Page1 : ContentPage
{
public static Button btnPageOne;
public Page1 ()
{
InitializeComponent ();
btnPageOne = btnInPage1;
}
}
Then in your DashboardPage, you can access the button by using Page1.btnPageOne, and handle the logic with:
Page1.btnPageOne.Clicked += delegate {
Console.WriteLine("Page1 btn clicked");
};
The same if you have Page2, Page3...

data binding content refreshing on pivotitem page

I develop an application, and faced a problem with following part.
I have a 'textblock' that binds exact item in observable collection of items('Items') and shows the number. Each item has a property, called 'Count'.
When user taps current textblock, which is in stackpanel of longlistselector (binding Items), I need this textblock to show new number(idea is each time user taps textblock, number increases by 1).
The only method I discovered is each time Navigate to current pivot item(where textblock is situated) to force pivot item's reload data from observable collection, where updated items(and 'Count' property) are stored. But it is slow and doesn't work good.
Please, advice how to make the number appear in the textblock each time user taps on current textblock.
<phone:PivotItem Name="Documents" Header="Documents" Margin="0" >
<Grid>
<phone:LongListSelector ItemsSource="{Binding Items}" >
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Height="95" >
<TextBlock Tap="Plus_Tap" Visibility="Visible" Width="20" Height="50" Text="{Binding Count}" FontFamily="Segoe WP Light"
FontSize="35" Foreground="White" Margin="2,0,0,2"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
</phone:PivotItem>
namespace PackMan
{
public partial class CategoryList : PhoneApplicationPage
{
public CategoryList()
{
InitializeComponent();
DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void Plus_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
PackList selectedList = (sender as TextBlock).DataContext as PackList;
selectedList.Count += 1;
NavigationService.Navigate(new Uri("/CategoryList.xaml?Documents", UriKind.Relative));
}
}
}
How to refresh the data in pivot item after updating data through xaml UI?
here a guy in the very end of his posted answer to his own question wrote: "If you are using the observableCollection than no need to refresh the page. It is the magic of ViewModel." But I load data from 'Items' observable collection, which is not in ViewModel, but declared in Packlist.cs. Maybe this will help.
Here I do increase Count property of the item in ObservableCollection "Items" :
private void Plus_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
PackList selectedList = (sender as TextBlock).DataContext as PackList;
selectedList.Count += 1;
I tap on textblock, and text of the textblock shows no changes. I tap return - which brings me back to menu page, than again I open pivot item page, and see the textblock's text(number) increases by one. Change is seen only when I reload page(pivot item), since I have this in its constructor and OnNavigatedTo method:
public CategoryList()
{
InitializeComponent();
DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
LoadData() - loads OBservable collection ("Items"), which has items, and Count as one of its properties:
public void LoadData()
{
this.Items = LoadNewLists();
this.IsDataLoaded = true;
}
Maybe now you could tell me how to make text of the textblock change instantly as I tap on it(textblock). I will be thankful for any advice, since this moment has stopped me from developing my app second day now.
I figured it out. I should have read about INotifyPropertyChanged Interface and created 'Count' property using this interface's method:
public int Count
{
get
{
return _count;
}
set
{
if (value != _count)
{
_count = value;
NotifyPropertyChanged("Count");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Hope it will be helpful to anyone.

Windows 8.1: Behaviors on Flyouts don't Work

I am developing a windows 8.1 app using VS 2013 and MVVM Light.
The following code shows the behavior in a flyout within an appbar:
<AppBarButton.Flyout>
<Flyout x:Name="FlyoutCalculator"
Placement="Top"
FlyoutPresenterStyle="{StaticResource FlyoutPresenterBaseStyle}">
<uc:Calculator ApplyCommand="{Binding CancelCommand}"
CancelCommand="{Binding CancelCommand}"
Available="{Binding AvailableCounter, Mode=OneWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Opening">
<core:InvokeCommandAction Command="{Binding ShowCurrentCostsCommand}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Flyout>
</AppBarButton.Flyout>
Unfortunately I get an exception while compiling the app:
WinRT-Informationen: Cannot add instance of type Microsoft.Xaml.Interactions.Core.EventTriggerBehavior to a collection of type Microsoft.Xaml.Interactivity.BehaviorCollection
Other Behaviors in the View do work, does someone know a solution to this?
Extremely late answer here, but I had the same issue and came up with a solution after finding this post.
I just created a custom behavior specifically for flyouts, used like this. OpenActions will execute when the flyout is opened, and CloseActions will execute when the flyout closes. In this case, I wanted the bottom app bar to not be visible when the flyout was open.
<Flyout Placement="Full">
<i:Interaction.Behaviors>
<behaviors:FlyoutBehavior>
<behaviors:FlyoutBehavior.OpenActions>
<core:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" TargetObject="{Binding ElementName=CommandBar}" />
</behaviors:FlyoutBehavior.OpenActions>
<behaviors:FlyoutBehavior.CloseActions>
<core:ChangePropertyAction PropertyName="Visibility" Value="Visible" TargetObject="{Binding ElementName=CommandBar}" />
</behaviors:FlyoutBehavior.CloseActions>
</behaviors:FlyoutBehavior>
</i:Interaction.Behaviors>
<Grid>
...
</Grid>
</Flyout>
Code is here:
class FlyoutBehavior : DependencyObject, IBehavior
{
public DependencyObject AssociatedObject { get; private set; }
public void Attach(Windows.UI.Xaml.DependencyObject associatedObject)
{
var flyout = associatedObject as FlyoutBase;
if (flyout == null)
throw new ArgumentException("FlyoutBehavior can be attached only to FlyoutBase");
AssociatedObject = associatedObject;
flyout.Opened += FlyoutOpened;
flyout.Closed += FlyoutClosed;
}
public void Detach()
{
var flyout = AssociatedObject as FlyoutBase;
if (flyout != null)
{
flyout.Opened -= FlyoutOpened;
flyout.Closed -= FlyoutClosed;
}
}
public static readonly DependencyProperty OpenActionsProperty =
DependencyProperty.Register("OpenActions", typeof(ActionCollection), typeof(FlyoutBehavior), new PropertyMetadata(null));
public ActionCollection OpenActions
{
get { return GetValue(OpenActionsProperty) as ActionCollection; }
set { SetValue(OpenActionsProperty, value); }
}
public static readonly DependencyProperty CloseActionsProperty =
DependencyProperty.Register("CloseActions", typeof(ActionCollection), typeof(FlyoutBehavior), new PropertyMetadata(null));
public ActionCollection CloseActions
{
get { return GetValue(CloseActionsProperty) as ActionCollection; }
set { SetValue(CloseActionsProperty, value); }
}
private void FlyoutOpened(object sender, object e)
{
foreach (IAction action in OpenActions)
{
action.Execute(AssociatedObject, null);
}
}
private void FlyoutClosed(object sender, object e)
{
foreach (IAction action in CloseActions)
{
action.Execute(AssociatedObject, null);
}
}
public FlyoutBehavior()
{
OpenActions = new ActionCollection();
CloseActions = new ActionCollection();
}
}
I do not have a solution but:
I'm not using Flyouts in my Windows 8.1 App, I'm using a UserControl on which I have added a EventTriggerBehavior as you did. And I get exactly the same Errormessage from VisualStudio at runtime.
As I am using a RoutedEventHandler this could cause the Problem as you use
EventHandler<object> Opening
as the Trigger for the Behavior. But that is just an idea of what is the problem.
For me I have found an answer:
I have changed the Type of my RoutedEventHandler to be just a normal EventHandler. And the Method inside the CodeBehind which triggers the RoutedEventHandler is invoked with only the sender, because I dont know how to convert RoutedEventArgs into EventArgs, but as long as I dont need the EventArgs it's not a problem.
You could also make a workaround by creating a UserControl with a Flyout Control and make the Opening Event public to the Page where you use it. Then you can add the EventTriggerBehavior to the UserControl and connect it to your custom Opening Event and you should get the expected behavior.

How to fire a button Click event in code behind in user control?

is it possible to fire a button Click event in code behind in user control?
In C# events can only be invoked from the class that declares them. In case of the Button there is a method called OnClick which raises the ClickEvent but it is protected. So you need to declare class that inherits from Button and change the visibility of OnClick method (or declare some over method that calls base.OnClick)
public class MyButton : Button
{
public new void OnClick()
{
base.OnClick();
}
}
Example of XAML
<StackPanel Background="White" >
<my:MyButton x:Name="TestButton" Click="HandleClick" Content="Test" />
<TextBlock x:Name="Result" />
</StackPanel>
And code behind:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
new Timer(TimerCall,null,0,1000);
}
private void TimerCall(object state)
{
Dispatcher.BeginInvoke(()=>TestButton.OnClick());
}
private void HandleClick(object sender, RoutedEventArgs e)
{
Result.Text = String.Format("Clicked on {0:HH:mm:ss}",DateTime.Now);
}
}
Though it is always easier to call event handler directly.
HandleClick(this,null)
Then there will be no need for extra plumbing.