How to change size of XAML flyout? - xaml

In my case size of flyout is calculated incorrectly.
<AppBarButton>
<AppBarButton.Flyout>
<Flyout>
<ListView>
<x:String>Short</x:String>
<x:String>Very very very long</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
</ListView>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
If a height of a window is big enough - all fine.
But if not - flyout width is calculated based on the first element (not on the biggest) and I see the following:
How can I change width of Flyout from code (or from XAML) ?
EDIT:
The following code works:
auto w = filesListView->ActualWidth.ToString();
Windows::UI::Xaml::Style^ s = ref new Windows::UI::Xaml::Style();
s->Setters->Append(ref new Setter(MinWidthProperty, w));
myFlyout->FlyoutPresenterStyle = s;
I tried to put it to Flyout.Opening(), Flyout.Opened(), ListView.SizeChanged(), ListView.Loaded(), ListView.Loading(), ListView.LayoutUpdated(). The problem is that it works starting from the second call. In the first call flyout still rendering with wrong width.

You can change the ListView width to change the Flyout width
<AppBarButton Margin="10,10,10,10">
<AppBarButton.Flyout>
<Flyout>
<ListView Width="500">
<x:String>Short</x:String>
<x:String>Very very very long</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
</ListView>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
I put all the code on github and you are welcome to visit
And the other way is set the FlyoutPresenterStyle
<AppBarButton Margin="10,10,10,10">
<AppBarButton.Flyout>
<Flyout>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="MinWidth" Value="500"></Setter>
</Style>
</Flyout.FlyoutPresenterStyle>
<ListView >
<x:String>Short</x:String>
<x:String>Very very very long</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
</ListView>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
See https://stackoverflow.com/a/54902492/6116637
I will also put all the code on github and welcome to visit.

I managed to change size of flyout by attaching flyout initially to another element, and overriding Flyout.Opened(), where correct width calculated and flyout attached to the right element.
XAML code:
<Page.Resources>
<Flyout x:Name="myFlyout" Opened="myFlyoutOpened">
<ListView x:Name="filesListView">
<x:String>Short</x:String>
<x:String>Very very very long</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
<x:String>Short</x:String>
</ListView>
</Flyout>
</Page.Resources>
<StackPanel>
<AppBarButton x:Name="myButton" Click="MyButton_Click"/>
<AppBarButton x:Name="SecondButton"/>
</StackPanel>
C++ code:
void App4::MainPage::MyButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
auto flyout = static_cast<Flyout^>(Resources->Lookup(L"myFlyout"));
flyout->ShowAt(SecondButton);
}
void App4::MainPage::myFlyoutOpened(Platform::Object^ sender, Platform::Object^ e)
{
auto w = filesListView->ActualWidth.ToString();
Windows::UI::Xaml::Style^ s = ref new Windows::UI::Xaml::Style();
s->Setters->Append(ref new Setter(MinWidthProperty, w));
myFlyout->FlyoutPresenterStyle = s;
myFlyout->ShowAt(myButton);
}

I have found another solution: it is enough to fill MaxHeight property of ListView:
void App4::MainPage::MyButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
filesListView->MaxHeight = Window::Current->Content->ActualSize.y - 50;
auto flyout = static_cast<Flyout^>(Resources->Lookup(L"myFlyout"));
flyout->ShowAt(SecondButton);
}

Related

Is there a way to change the parent content for a popup in WinUI3?

In WinUI3 it seems that you are unable to modify the duration of a tooltip (similar to how WPF allowed a duration change with the ToolTipService.ShowDuration).
As such, I'm attempting to replicate the behavior using a Popup control such that the Popup will remain visible as long as the mouse is hovered over an element (utilizing the element's OnPointerEntered / OnPointerExited events to modify the IsOpen property of the Popup).
This displays the Popup correctly, however in my case the Popup exists inside a Grid which is inside the ListView.ItemTemplate for a ListView. ala:
<ListView>
<DataTemplate>
<Grid>
<TextBlock/>
<Popup/>
</Grid>
</DataTemplate>
</ListView>
Thus the popup only overlays the <Grid>, but still gets clipped within the region of the DataTemplate.
I'd like to have the popup overlay the <ListView> itself instead - is there a way to specify which content the popup will overlay such that it will overlay the <ListView>?
Or even better - to have it overlay all elements of the Window (replicating the placement of a tooltip?)
Is there a way to change the parent content for a popup in WinUI3?
Sure, you could place Popup at same level as ListView in current page. And listen ListView item PointerEntered and PointerExited event then pass current item datacontext to popup control.
Xaml
<ListView
Margin="0,10,0,45"
ItemsSource="{x:Bind Items}"
Visibility="Visible">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Vertical"
PointerEntered="StackPanel_PointerEntered"
PointerExited="StackPanel_PointerExited">
<TextBlock x:Name="Id" Text="{Binding ID}" />
<TextBox
x:Name="FirstName"
GettingFocus="FirstName_GettingFocus"
Text="{Binding FirstName}" />
<TextBox x:Name="LastName" Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Popup
x:Name="PopupTip"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Bottom" />
</TransitionCollection>
</Popup.ChildTransitions>
<StackPanel
Width="150"
Height="80"
Background="LightGray"
CornerRadius="4">
<TextBlock
x:Name="InfoLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding ID}" />
</StackPanel>
</Popup>
Code behind
private void StackPanel_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var ItemDataContext = (sender as FrameworkElement).DataContext;
PopupTip.DataContext = ItemDataContext;
PopupTip.IsOpen = true;
}
private void StackPanel_PointerExited(object sender, PointerRoutedEventArgs e)
{
PopupTip.DataContext = null;
PopupTip.IsOpen = false;
}

How to make TextBlock selectable in Flyout (XAML)?

My XAML code:
<AppBarButton>
<AppBarButton.Flyout>
<Flyout>
<TextBlock Text="Text is not selectable here" IsTextSelectionEnabled="True"/>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
Despite I set IsTextSelectionEnabled - the text is not selectable in Flyout.
Is it possible to make the text selectable ? I tried RichTextBlock - it doesn't work as well.
You could try to set AllowFocusOnInteraction property as true on the AppBarButton to get focus.
<AppBarButton AllowFocusOnInteraction="True">
<AppBarButton.Flyout>
<Flyout>
<TextBlock Text="Text is not selectable here" IsTextSelectionEnabled="True"/>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>

How to attach Flyout to MenuFlyoutItem?

XAML code:
<Page.TopAppBar>
<CommandBar x:Name="bottomAppBar" Padding="10,0,10,0">
<AppBarButton AutomationProperties.Name="Sample Button"
AutomationProperties.AutomationId="SampleAppBarButton"
Click="AppBarButton_Click">
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Name="MuteMenu" Icon="Mute" Text="Mute" Click="MuteMenu_Click">
<FlyoutBase.AttachedFlyout>
<Flyout>
<TextBlock Text="Some text..."/>
</Flyout>
</FlyoutBase.AttachedFlyout>
</MenuFlyoutItem>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
</CommandBar>
</Page.TopAppBar>
C++/CX:
void App2::DirectXPage::MuteMenu_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
FlyoutBase::ShowAttachedFlyout((FrameworkElement^)sender);
}
But ShowAttachedFlyout not working - flyout is not appearing when I click menu item. No errors reported.
Creating and attaching flyout programmatically doesn't work as well.
Target version is 10.0.18362.0.
Visual Studio 2019 (v142).
flyout is not appearing when I click menu item
By testing your code, the flyout doesn't appear is because after the MenuFlyoutItem is clicked, the entire MenuFlyout will be hidden, the Flyout inside it can't appear.
You can try to use MenuFlyoutSubItem which contains a cascading list of menu items.
<Page.TopAppBar>
<CommandBar x:Name="bottomAppBar" Padding="10,0,10,0">
<AppBarButton AutomationProperties.Name="Sample Button"
AutomationProperties.AutomationId="SampleAppBarButton"
x:Name="MyAppButton" >
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyoutSubItem x:Name="MuteMenu" Icon="Mute" Text="Mute">
<MenuFlyoutItem Text="Some Text..."></MenuFlyoutItem>
<MenuFlyoutItem Text="123"></MenuFlyoutItem>
</MenuFlyoutSubItem>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
</CommandBar>
</Page.TopAppBar>
Or you can add a Button.Flyout and include the menuFlyout in the Button.Flyout.
<Page.TopAppBar>
<CommandBar x:Name="bottomAppBar" Padding="10,0,10,0">
<AppBarButton x:Name="button" >
<AppBarButton.Flyout>
<Flyout>
<StackPanel>
<Button>
<StackPanel Orientation="Horizontal">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph=""></FontIcon>
<TextBlock>Mute</TextBlock>
</StackPanel>
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem Text="Some text..."></MenuFlyoutItem>
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
</Flyout>
</AppBarButton.Flyout>
</CommandBar>
</Page.TopAppBar>

How to do relative binding for button flyout in universal windows store app

I want to bind my button flyout width and height with some other control but it not working
<Grid >
<Popup IsOpen="True" Name="popup">
<Grid Name="popupBorder" Background="Red" Height="100" Width="100" >
</Grid>
</Popup>
<Button Content="check flyout" Name="btn" Click="Button_Click" >
<Button.Flyout>
<Flyout>
<Border Name="flyoutBorder" Height="{Binding Path=Height, ElementName=popupBorder, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
Width="{Binding Path=Width, ElementName=popupBorder, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}">
</Border>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
I have also given datacontext to button but still same
DataContext="{Binding ElementName=localContext, Mode=OneWay}"
When you bind data in the content of Flyout, the binding source is in Page, and binding target is in PopupRoot, they have different DataContext, so can't it work here.
Besides, a Flyout control is like this:
If you place a Border as Content of Flyout, it will be placed in the ScrollContentPresenter:
As you can see, if you set the Width and Height of content, it will not effect the size of the Flyout since there is a ScrollViewer inside it: ScrollViewer's content has unlimited height and width.
I want to bind my button flyout width and height with some other control but it not working
So, the right way to custom the size of Flyout is to style the FlyoutPresenter for example like this:
<Flyout>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="100" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Border Name="flyoutBorder" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</Border>
</Flyout>
Here we need to use MinHeight and MinWidth to set the size of Flyout, but since you want to bind Width and Height to some other control, and the Windows Runtime doesn't support a Binding usage for Setter.Value (the Binding won't evaluate and the Setter has no effect, you won't get errors, but you won't get the desired result either).
Usually when need data binding in Setter.Value, we can create some attached dependency properties. And to solve the different DataContext problem, we need to define the property in code behind or view model for example like this:
public static double NewWidth { get; set; } = 100.0;
Then bind the this property to the Width of popupBorder:
<Grid>
<Popup IsOpen="False" Name="popup">
<Grid Name="popupBorder" Background="Red" Height="100" Width="{Binding NewWidth}">
</Grid>
</Popup>
<Button Content="check flyout" Name="btn" Click="Button_Click" Foreground="Black">
<Button.Flyout>
<Flyout>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="local:BindingBuilder.FlyoutWidth" Value="400" /> <!--This value has no meaning here, you can set it to any value.-->
<Setter Property="MinHeight" Value="100" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Border Name="flyoutBorder" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</Border>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
To register attached property, you can for example code like this:
public class BindingBuilder : DependencyObject
{
public static readonly DependencyProperty FlyoutWidthProperty =
DependencyProperty.RegisterAttached("FlyoutWidth", typeof(double), typeof(BindingBuilder), new PropertyMetadata(0, OnFlyoutWidthChanged));
public static double GetFlyoutWidth(DependencyObject obj)
{
return (double)obj.GetValue(FlyoutWidthProperty);
}
public static void SetFlyoutWidth(DependencyObject obj, double value)
{
obj.SetValue(FlyoutWidthProperty, value);
}
public static void OnFlyoutWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double newFlyoutWidth = (double)d.GetValue(FlyoutWidthProperty);
var presenter = (FlyoutPresenter)d;
presenter.MinWidth = MainPageViewModel.NewWidth;
}
}
Here I only attached the MinWidth property, you can also use the same method to custom the MinHeight of FlyoutPresenter.

How to set DataContext for all Page or for AppBarButton

I have XAML page that looks like:
<Page
xmlns:vm="using:domain.viewmodels">
<Page.Resources>
<vm:MainPageViewModel x:Name="MainPageVm"/>
</Page.Resources>
<Grid DataContext="{Binding Source={StaticResource MainPageVm}}">
<Button Command={Binding QuitCommand, Mode=TwoWay}"/>
</Grid>
<Page.BottomAppBar>
<CommandBar IsOpen="True">
<AppBarButton Label="Quit" Command={Binding QuitCommand, Mode=TwoWay}"/>
</CommandBar>
</Page.BottomAppBar>
</Page>
If I press the Button – the Command was executed.
If I press the AppBarButton – the Command was not executed.
I get that the reason is that I have set DataContext for Grid only, but not for BottomAppBar. But I don't know how to set DataContext for all Page.
I have tried this code, but it doesn't work because I have declared MainPageVm below:
<Page
DataContext="{Binding Source={StaticResource MainPageVm}}"
xmlns:vm="using:domain.viewmodels">
<Page.Resources>
<vm:MainPageViewModel x:Name="MainPageVm"/>
</Page.Resources>
I thought I can set DataContext for CommandBar. I don't like this solution, as I need to set DataContext twice for the Page, but even this solution doesn't work:
<Page.BottomAppBar>
<CommandBar IsOpen="True" DataContext="{Binding Source={StaticResource MainPageVm}}">
<AppBarButton Label="Quit" Command={Binding QuitCommand, Mode=TwoWay}"/>
</CommandBar>
</Page.BottomAppBar>
Why?
Instead of using Resources, set the DataContext directly:
<Page
xmlns:vm="using:domain.viewmodels">
<Page.DataContext>
<vm:MainPageViewModel x:Name="MainPageVm"/>
</Page.DataContext>
Your bindings become then:
<Grid>
<Button Command={Binding QuitCommand}"/>
</Grid>
<Page.BottomAppBar>
<CommandBar IsOpen="True">
<AppBarButton Label="Quit" Command={Binding QuitCommand}"/>
</CommandBar>
</Page.BottomAppBar>
It's often simpler to declare your DataContext in code-behind:
public YourPageClass()
{
InitializeComponent();
this.DataContext = new MainPageVM();
}