Caliburn-Micro / Caliburn.Micro

A small, yet powerful framework, designed for building applications across all XAML platforms. Its strong support for MV* patterns will enable you to build your solution quickly, without the need to sacrifice code quality or testability.
http://caliburnmicro.com/
MIT License
2.79k stars 775 forks source link

View.Model not support ParentViewModel Action? #868

Closed sdwdjzhy closed 11 months ago

sdwdjzhy commented 12 months ago

now ,i use Drawer control of HandyControls ,

 <!-- this code is in ParentView -->
    <hc:SimplePanel>
        <Button x:Name="Open" Content="Open" />
        <hc:Drawer
            Dock="Right"
            IsOpen="{Binding IsOpen}"
            MaskCanClose="False"
            ShowMode="Cover">
            <Border
                Width="500"
                Padding="20"
                Style="{StaticResource BorderRegion}">
                <ContentControl cm:View.Model="{Binding CView}" />
            </Border>
        </hc:Drawer>
    </hc:SimplePanel>

  <!-- this code is in ChildView -->
    <StackPanel>
        <TextBlock Text="This is ChildView" />
        **<Button cm:Message.Attach="Close" Content="Close" />**
    </StackPanel>

i found when i use ParentView - ChildView only ,it is ok. The Close action in ParentViewModel is work.

await DisplayRootViewForAsync<ParentViewModel>();

but when i start with MainView (extends Conductor.Collection.OneActive), it is not work

await DisplayRootViewForAsync<MainViewModel>();

test project

vb2ae commented 11 months ago

Thanks for the test project I will look at this

vb2ae commented 11 months ago

When you use the Conductor the ChildViewModel because it is inheriting from ViewAware basically uses the MainViewModel for the events.

Here is one possible solution to your issue. You could keep a reference to the ParentViewModel and add the open and close methods to MainViewModel and call the code in the ParentViewModel when the buttons are clicked. (Note I like to use dependency injection)

  internal class MainViewModel : Conductor<Screen>.Collection.OneActive
 {
      private readonly ParentViewModel _parentViewModel;
      public MainViewModel(ParentViewModel parentViewModel)
     {
          _parentViewModel = parentViewModel;
           Task.Run(async () =>
     {
         await ActivateItemAsync(_parentViewModel);
     });
 }

 public void Open()
 {
     _parentViewModel.IsOpen = true;
 }

 public void Close()
 {
     _parentViewModel.IsOpen = false;
 }

}

sdwdjzhy commented 11 months ago

Although this is a way, I don't think it's particularly good.

In my project, MainViewModel is just a facade and does not participate in business logic, while ParentViewModel is the specific business interface. If this approach is adopted, it clearly violates the principle of isolation. I think this should be a bug.

I have now used IEventAggregator as the middle layer.

I haven't fully understood Calibum yet, and I will try other methods to solve this bug later. Perhaps after getting familiar with it for a while, we can see if we can solve it again? I'm not sure

vb2ae commented 11 months ago

In your example the conductors page there is only a frame. The conductor page could also host other controls like a toolbar for example. The change you are asking for could break other users. I will look into making a property to override which viewmodel is using by default

sdwdjzhy commented 11 months ago

Thank you for your reply. I have found the key point of this problem. it is not a Bug of Calibum.

I debug ActionMessage.cs Drawer of handycontrol contains AdornerDecorator when debug method SetMethodBinding of ActionMessage.cs the parent of AdornerDecorator is Window, so SetMethodBinding cannot find ParentView.

So I found the source code for HandyControl

        AdornerDecorator decorator;
        var parent = VisualHelper.GetParent<DrawerContainer>(this);
        if (parent != null)
        {
            _contentElement = parent.Child;
            decorator = parent;
        }
        else
        {
            var window = WindowHelper.GetActiveWindow();
            if (window == null)
            {
                return;
            }

decorator = VisualHelper.GetChild<AdornerDecorator>(window);
            _contentElement = window.Content as UIElement;
        }

Now, I added a layer of hc: DrawerContainer outside the hc:Drawer, and it works

    <hc:SimplePanel Name="simm">
        <Button x:Name="Open" Content="Open" />

        <hc:DrawerContainer>

            <hc:Drawer
                Dock="Right"
                IsOpen="{Binding IsOpen}"
                MaskCanClose="False"
                ShowMode="Cover">
                <Border
                    Width="500"
                    Padding="20"
                    Style="{StaticResource BorderRegion}">

                    <ContentControl cm:View.Model="{Binding CView}" />
                </Border>
            </hc:Drawer>
        </hc:DrawerContainer>
    </hc:SimplePanel>