CommunityToolkit / Windows

Collection of controls for WinUI 2, WinUI 3, and Uno Platform developers. Simplifies and demonstrates common developer tasks building experiences for Windows with .NET.
https://aka.ms/windowstoolkitdocs
Other
555 stars 71 forks source link

GridSplitter Style loading issue when used in code-behind only #273

Open zou-z opened 11 months ago

zou-z commented 11 months ago

Describe the bug

There are two GridSplitter for winui3 that we can use now. first come from CommunityToolkit.WinUI.Controls.Sizer NuGet package. this GridSplitter will throw an unhandled exception when i add it in cs code. the other GridSplitter come from CommunityToolkit.WinUI.UI.Controls.Layout. sometime i have to drag hard to make it work.

Steps to reproduce

(1) GridSplitter (CommunityToolkit.WinUI.Controls.Sizer)
public class TestTest : UserControl
{
    public TestTest()
    {
        var grid = new Grid();
        Content = grid;
        grid.Children.Add(new GridSplitter());
    }
}
add this user control to main page and start application.then it throw an unhandled exception.
(2) GridSplitter (CommunityToolkit.WinUI.UI.Controls.Layout)
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.03125*"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="0.03125*"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="0.0625*"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="0.125*"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="0.25*"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="0.5*"/>
    </Grid.ColumnDefinitions>
    <Border Grid.Column="0" Background="Red"/>
    <controls:GridSplitter Grid.Column="1"/>
    <Border Grid.Column="2" Background="Green"/>
    <controls:GridSplitter Grid.Column="3"/>
    <Border Grid.Column="4" Background="Red"/>
    <controls:GridSplitter Grid.Column="5"/>
    <Border Grid.Column="6" Background="Green"/>
    <controls:GridSplitter Grid.Column="7"/>
    <Border Grid.Column="8" Background="Red"/>
    <controls:GridSplitter Grid.Column="9"/>
    <Border Grid.Column="10" Background="Green"/>
</Grid>
add several control and GridSplitter one by one. from last to first drag the GridSplitter.you will find the front GridSplitter is hard to drag.

Expected behavior

  1. can add and remove in cs code.
  2. can drag normally.

Screenshots

(1) GridSplitter (CommunityToolkit.WinUI.Controls.Sizer) image (2) GridSplitter (CommunityToolkit.WinUI.UI.Controls.Layout) https://github.com/CommunityToolkit/Windows/assets/45748083/dbfca4ff-7194-4eb2-ac2d-0b7c32724066

Code Platform

Windows Build Number

Other Windows Build number

No response

App minimum and target SDK version

Other SDK version

No response

Visual Studio Version

2022

Visual Studio Build Number

17.7.5

Device form factor

Desktop

Additional context

CommunityToolkit.WinUI.Controls.Sizer 8.0.230907 CommunityToolkit.WinUI.UI.Controls.Layout 7.1.2 Microsoft.Windows.SDK.BuildTools 10.0.22621.756 Microsoft.WindowsAppSDK 1.4.230913002

Help us help you

Yes, I'd like to be assigned to work on this item.

Arlodotexe commented 11 months ago

The 7.x packages are out of date, the dragging bug should be fixed in the latest CommunityToolkit.WinUI.Controls.Sizer 8.0.230907.

Could you clarify what your goal is? If you're trying to extend GridSplitter, you should inherit from it or SizerBase directly.

GridSplitter isn't designed to be used in an empty grid, as you've done in your repro. You can find example usage here, and more examples for Sizer controls here.

zou-z commented 11 months ago

I want add GridSplitter when i need it.But is seems i have to add it to xaml from the beginning.

michael-hawker commented 11 months ago

@zou-z you can change its visibility if you only need it around sometimes.

michael-hawker commented 11 months ago

Off-topic question: why on earth set a relative size for the spitter? Shouldn't these be a fixed size, like 8?

Think I got lost... it is a fixed size by default. I'm not sure where relative sizing came in? Maybe good for a chat in the UWP Discord or a discussion?

Jay-o-Way commented 11 months ago

Nope. My bad. Scratch that. I need to double check before pointing at things. 🤐🤦🏻‍♂️

zou-z commented 11 months ago

ok.But it should not throw exception no matter how I use it.

zou-z commented 11 months ago

I found the problem is that the default style of SizerBase can not apply to SizerBase.So I copy the SizerBase.xaml code to App.xaml.And add a name for default SizerBase style.

<Application 
  xmlns:controls="using:CommunityToolkit.WinUI.Controls"
  ...
  <Style x:Key="SizerBaseStyle" TargetType="controls:SizerBase">
  ...
  </Style>
  ...
</Application>

Then I set style in SizerBase constructor

public SizerBase()
{
    this.DefaultStyleKey = typeof(SizerBase);
    string styleName = "SizerBaseStyle";
    if (Application.Current.Resources.ContainsKey(styleName))
    {
        if (Application.Current.Resources[styleName] is Style style)
        {
            Style = style;
        }
    }
}

Write some code test it

// test class
class TestClass : Grid
{ 
    public void Test()
    {
        ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });

        var border1 = new Border { Background = new SolidColorBrush(Colors.Red) };
        var splitter = new CommunityToolkit.WinUI.Controls.GridSplitter();
        var border2 = new Border { Background = new SolidColorBrush(Colors.Green) };
        SetColumn(border1, 0);
        SetColumn(splitter, 1);
        SetColumn(border2, 2);

        Children.Add(border1);
        Children.Add(splitter);
        Children.Add(border2);
    }
}

// add control to main window
<local:TestClass x:Name="testClass"/>

// in button click
private void Button_Click(object sender, RoutedEventArgs e)
{
    testClass.Test();
}

It is worked.

zou-z commented 11 months ago

I got lost too.After I delete the GridSplitter from xaml file.It would apply style.After set Style = style;.The Style property is still null.I can't believe eyes.So I realized I must add a GridSplitter to xaml file. for example:

<controls:GridSplitter Visibility="Collapsed"/>

In wpf we can easily to create our user control from FrameworkElement.In conclusion I decide to make SizerBase inherit from UserControl instead of Control.I guess there is a default style in default Generic.xaml for UserControl.So I not need add a xaml file to define the control template and apply it to the corresponding control. My code:

public abstract partial class SizerBase : UserControl
{
    ....
    public SizerBase()
    {
        Content = new Border { MinWidth = 10, MinHeight = 10, Background = new SolidColorBrush(Colors.Transparent) };
        ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;
        OnApplyTemplate();
    }
    ....
}

This time I can use GridSplitter what ever I want.

michael-hawker commented 9 months ago

You shouldn't have to copy the style to your app nor re-write the SizerBase control. This should just work. We are doing different things here with inheritance and styles, so I wonder if this is just exposing an issue in WinUI 3 though, as we have seen issues there with control styles in the past as well.

Would be curious to see if the same issue happens in UWP. We do already have tests for these controls, but could try adding your simple test case:

        var grid = new Grid();
        Content = grid;
        grid.Children.Add(new GridSplitter());

Though again, we don't really expect the control to be used in a completely empty grid like this. It would be good to get more details of the exception and what's actually going on.

abdes commented 9 months ago

@zou-z is right. When you use the Gridsplitter control from code behind, we get the following exceptions:

Exception thrown at 0x00007FF90DD84D8C (KernelBase.dll) in Routing.Debugger.exe: WinRT originate error - 0x800F1000 : 'Cannot apply a Style with TargetType 'CommunityToolkit.WinUI.Controls.SizerBase' to an object of type 'Microsoft.UI.Xaml.Controls.Control'.'.
Microsoft.ui.xaml.dll!00007FF88FC6048C: 800F1000 - E_NER_INVALID_OPERATION
Exception thrown at 0x00007FF90DD84D8C (KernelBase.dll) in Routing.Debugger.exe: WinRT originate error - 0x800F1000 : 'No installed components were detected.

Basically, there is a problem applying the style defined for SizerBase to the control created from code behind. Note that the control is created in a populated grid, with all the properties correctly set.

The style is defined as following in https://github.com/CommunityToolkit/Windows/blob/0cb8b97e30baa01f5d9fd37a00ab634247ecabf7/components/Sizers/src/SizerBase.xaml#L49

    <Style TargetType="controls:SizerBase">
        <Setter Property="IsTabStop" Value="True" />
        <Setter Property="UseSystemFocusVisuals" Value="True" />
...
    </Style>

By copying the style, naming it and forcing the Style property, we are simply bypassing that style application to everything with SizerBase type.

The question is: Why is WinUI/WhoEver thinking that the GridSplitter control that was added to the grid is of type Control and not GridSplitter which drives from SizerBase?

EDIT: With more testing, copying the styles and using them as <Style TargetType="controls:SizerBase"> will produce the same exception. Only if they are specifically used as `<Style TargetType="mine:MyGridSplitter"> where MyGridSplitter is derived from GridSplitter and setting the style using a StaticResource from the copied one, works.

LucaCris commented 2 days ago

Today I have the same problem: GridSplitter created in code behind is not visible and crash if app is in Run instead of Debug.