Search code examples
c#xamlwinui-3winuiwindows-app-sdk

How to create a control that is able to be expanded bigger than Parent control in WinUI 3


I would like to create a control that can be expanded and is able to be shown even though the size is bigger than its parent control. I need a TextBox to input something and expand the control when text entered to show some buttons. Just like when the AutoSuggestBox or web browser's address bar received suggestions, the area is expanded and float on the surface.

image of AutoSuggestBox in winui3this is an AutoSuggestBox

I have tried to put anything I want to show in a Flyout and then make the Flyout floats over the original controls.

<Flyout x:Name="myFlyout">
    <Flyout.FlyoutPresenterStyle>
        <Style TargetType="FlyoutPresenter">
            <Setter Property="Margin" Value="0,-15,0,0"/>
            <!-- -15 margin makes it float over the attached control-->
        </Style>
    </Flyout.FlyoutPresenterStyle>
    <Grid x:Name="ContentArea" Width="800">
        <StackPanel x:Name="FlyoutStack">
        </StackPanel>
    </Grid>
</Flyout>
public void ShowFlyout()
{
    /**
    BaseGrid is the grid contains textbox
    TextBoxPart is a TextBox
    And the Flyout is attached to BaseGrid.
    */
    BaseGrid.Children.Remove(TextBoxPart);
    // move the textbox from BaseGrid to Flyout
    try { FlyoutStack.Children.Insert(0, SearchBoxPart); }
    catch (Exception e){ }
    SearchBoxFlyout.ShowAt(BaseGrid); //show the flyout
}

But when the text is in complex language. Moving the textbox will cause the received character disappeared. Is there any alternative way to expend the control?

Update

Here is my new code, SearchBox is my class.

public static readonly DependencyProperty TextBoxText = DependencyProperty.Register(
    nameof(myText),
    typeof(string), 
    typeof(SearchBox),
    new PropertyMetadata(default, (sender, args) =>
    {
        ((SearchBox)sender).TextChanged();
    }));

public string myText
{
    get => (string)GetValue(TextBoxText);
    set => SetValue(TextBoxText, value);
}

public void TextChanged()
{
    Debug.WriteLine($"{TextBoxControl.Text}////{TextBoxOnFlyout.Text}////{myText}");
// When I enter text in TextBoxControl myText Changed but TexBoxOnFlyout doesn't
}
<Grid x:Name="BaseGrid">
    <TextBox
        x:Name="TextBoxControl"
        Text="{x:Bind myText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <FlyoutBase.AttachedFlyout>
        <Flyout x:Name="SearchBoxFlyout">
            <Grid x:Name="ContentArea">
                <StackPanel x:Name="FlyoutStack">
                    <TextBox x:Name="TextBoxOnFlyout" Text="{x:Bind myText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                </StackPanel>
            </Grid>
        </Flyout>
    </FlyoutBase.AttachedFlyout>
</Grid>

Solution

  • Instead of moving TextBoxes, you can just create a DependencyProperty and bind it to each TextBox.

    For example:

    FlyoutTextBox.xaml

    <UserControl
        x:Class="WinUIApp.FlyoutTextBox"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="using:WinUIApp10"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <Grid>
            <TextBox
                x:Name="TextBoxControl"
                Text="{x:Bind Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <Grid.ContextFlyout>
                <Flyout
                    x:Name="Flyout"
                    Placement="Bottom">
                    <TextBox
                        Loaded="FlytoutTextBox_Loaded"
                        Text="{x:Bind Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </Flyout>
            </Grid.ContextFlyout>
        </Grid>
    
    </UserControl>
    

    FlyoutTextBox.xaml.cs

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    
    namespace WinUIApp;
    
    public sealed partial class FlyoutTextBox : UserControl
    {
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(
                nameof(Text),
                typeof(string),
                typeof(FlyoutTextBox),
                new PropertyMetadata(default, (d, e) => (d as FlyoutTextBox)?.OnTextChanged()));
    
        public FlyoutTextBox()
        {
            InitializeComponent();
        }
    
        public string Text
        {
            get => (string)GetValue(TextProperty);
            set => SetValue(TextProperty, value);
        }
    
        private void OnTextChanged()
        {
            if (string.IsNullOrEmpty(Text) is false)
            {
                Flyout.ShowAt(TextBoxControl);
            }
        }
    
        private void FlytoutTextBox_Loaded(object sender, RoutedEventArgs e)
        {
            if (sender is not TextBox textBox)
            {
                return;
            }
    
            textBox.SelectionStart = textBox.Text.Length;
        }
    }