SagiK-Repository / POC_.Net_DevExpress

✔ [POC] .Net DevExpress를 분석합니다.
0 stars 0 forks source link

dxmvvm:BooleanToVisibilityConverter and window.IsLoaded issue #19

Closed SAgiKPJH closed 6 months ago

SAgiKPJH commented 6 months ago

문제 원인

image

SAgiKPJH commented 6 months ago

문제 추측

image

SAgiKPJH commented 6 months ago

문제 발견

image

<Window x:Class="Client.Presentation.UI.LoginView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        mc:Ignorable="d"
        d:DesignHeight="450"
        d:DesignWidth="800"
        xmlns:common="clr-namespace:Client.Presentation.Common.DI"
        xmlns:viewModels="clr-namespace:Client.Business;assembly=Client.Business"
        xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
        DataContext="{common:DISource Type={x:Type viewModels:LoginViewModel}}">
    <Window.Resources>

        <!--<BooleanToVisibilityConverter x:Key="BooleanToVisibility" /> --> <!-- IsLoaded = false -->
        <dxmvvm:BooleanToVisibilityConverter x:Key="BooleanToVisibility" />        <!-- IsLoaded = true -->
    </Window.Resources>

    <Window.Visibility>
        <Binding Path="IsViewVisible"
                 Mode="OneWay"
                 Converter="{StaticResource BooleanToVisibility}" />
    </Window.Visibility>

    <Grid Background="White">
    </Grid>
</Window>
// LoginViewModel.cs
public class LoginViewModel : ViewModelBase
{
    public bool IsViewVisible
    {
        get { return GetProperty(() => IsViewVisible); }
        set { SetProperty(() => IsViewVisible, value); }
    }

    public LoginViewModel()
    {
    }
}
// App.xaml
protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            Services = ConfigureServices();

            DISource.Resolver = Resolve;

            var loginView = new LoginView();
            loginView.Show();
            loginView.IsVisibleChanged += (s, ev) =>
            {
                if (!loginView.IsVisible && loginView.IsLoaded) // in this point
                {
                    var mainView = new MainWindow();
                    mainView.Show();
                    loginView.Close();
                }
            };
        }


문제 원인 추측

SAgiKPJH commented 6 months ago

문제원인.pptx

SAgiKPJH commented 6 months ago

DevExpress Converter와 동일하게 구현한 결과

SAgiKPJH commented 6 months ago

DevExpress 질문

SAgiKPJH commented 6 months ago

DevExpress 질문 내용

Hello I Have some ploblem

<Window x:Class="Client.Presentation.UI.LoginView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        mc:Ignorable="d"
        d:DesignHeight="450"
        d:DesignWidth="800"
        xmlns:common="clr-namespace:Client.Presentation.Common.DI"
        xmlns:viewModels="clr-namespace:Client.Business;assembly=Client.Business"
        xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
        DataContext="{common:DISource Type={x:Type viewModels:LoginViewModel}}">
    <Window.Resources>

        <!--<BooleanToVisibilityConverter x:Key="BooleanToVisibility" /> --> <!-- IsLoaded = false -->
        <dxmvvm:BooleanToVisibilityConverter x:Key="BooleanToVisibility" />        <!-- IsLoaded = true -->
    </Window.Resources>

    <Window.Visibility>
        <Binding Path="IsViewVisible"
                 Mode="OneWay"
                 Converter="{StaticResource BooleanToVisibility}" />
    </Window.Visibility>

    <Grid Background="White">
    </Grid>
</Window>
// LoginViewModel.cs
public class LoginViewModel : ViewModelBase
{
    public bool IsViewVisible
    {
        get { return GetProperty(() => IsViewVisible); }
        set { SetProperty(() => IsViewVisible, value); }
    }

    public LoginViewModel()
    {
    }
}
// App.xaml
protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            Services = ConfigureServices();

            DISource.Resolver = Resolve;

            var loginView = new LoginView();
            loginView.Show();
            loginView.IsVisibleChanged += (s, ev) =>
            {
                if (!loginView.IsVisible && loginView.IsLoaded) // in this point
                {
                    var mainView = new MainWindow();
                    mainView.Show();
                    loginView.Close();
                }
            };
        }

When closing the window, the IsVisibleChanged event is triggered. Depending on whether you choose BooleanToVisibilityConverter or dxmvvm:BooleanToVisibilityConverter, the IsLoaded part in the cs code of App.xaml appears as either false or true. This prevents me from using dxmvvm:BooleanToVisibilityConverter.

I'm curious about the cause of this issue and a possible solution.

SAgiKPJH commented 6 months ago

코드 분석


            var loginView = new LoginView();
            loginView.Show();
            loginView.IsVisibleChanged += (s, ev) =>
            {
                if (!loginView.IsVisible && loginView.IsLoaded) // in this point
                {
                    var mainView = new MainWindow();
                    mainView.Show();
                    loginView.Close();
                }
            };

위 코드를 사용하는 이유

SAgiKPJH commented 6 months ago

제안

코드 제안

SAgiKPJH commented 6 months ago

추가 질문

추가 질문

I apologize for the inconvenience.

It seems that there is a problem when there is a Binding element inside the Grid. I accidentally omitted and deleted the XAML code.

If there is a Binding element as follows, there may be an issue with the use of dxmvvm:BooleanToVisibilityConverter.

I share the file and video with you.

<Window.DataContext>
        <common:LoginViewModel />
    </Window.DataContext>
    <Window.Resources>

        <!--<BooleanToVisibilityConverter x:Key="BooleanToVisibility" />--> <!-- IsLoaded = false -->
        <dxmvvm:BooleanToVisibilityConverter x:Key="BooleanToVisibility" /> <!-- IsLoaded = true -->
    </Window.Resources>

    <Window.Visibility>
        <Binding Path="IsViewVisible"
                 Mode="OneWay"
                 Converter="{StaticResource BooleanToVisibility}" />
    </Window.Visibility>

    <Grid Background="White">
        <TextBox Text="{Binding Id}" />
    </Grid>
public class LoginViewModel : ViewModelBase
{
    public string Id
    {
        get { return GetProperty(() => Id); }
        set { SetProperty(() => Id, value); }
    }

    public bool IsViewVisible
    {
        get { return GetProperty(() => IsViewVisible); }
        set { SetProperty(() => IsViewVisible, value); }
    }

    public LoginViewModel()
    {
    }
}

I'm curious about the cause of this problem and possible solutions.

SAgiKPJH commented 6 months ago

답변

Thank you for the clarification. I reproduced this issue and researched it.

I found that BooleanToVisibilityConverter itself does not cause the issue. When I copy its implementation to a sample project, the result is similar to the standard converter. It occurs due to extra code executed when DevExpress components are used, and you'll see the same behavior if you use, for example, SimpleButton from our suite. However, the most interesting thing is that it's very easy to reproduce this behavior without DevExpress at all - for this you need to subscribe to the Loaded event for a control within a window. Please take a look at the modified sample where I demonstrated this.

Further research shows that this happens due to the SubtreeHasLoadedChangeHandler property value check in the IsLoaded getter - this is the specificity we cannot control at the level of our components.

Since there is no connection between IsLoaded and IsVisible properties in a general case, I recommend you re-work your current solution to make it more reliable. For example:

Show LoginView as a modal window to make sure that MainWindow is created after LoginView is closed;

var loginView = new LoginView();
loginView.ShowDialog();
var mainView = new MainWindow();
mainView.Show();

Handle the Closing/Closed event for LoginView instead of IsVisibleChanged. Thanks

SAgiKPJH commented 6 months ago

답변 정리