-1

I've a view in WPF where I need to center a canvas within a canvas. I know it's not the most suitable container for this, but we have other components that will come in this canvas where it will simplify a lot our job.

So basically, I've currently this code:

<Canvas Name="RootContainer" Background="#373B3F" ClipToBounds="True" MouseLeftButtonDown="OnMainCanvasMouseLeftButtonDown">
    <Canvas.DataContext>
        <local:SomeViewModel/>
    </Canvas.DataContext>
    
    <Canvas Name="SomeContainer" Background="#373B3F" MouseMove="OnCanvasMouseMove" MouseWheel="OnCanvasMouseWheel">
        <Canvas.Left>
            <MultiBinding Converter="{StaticResource CenterValueConverter}">
                <Binding ElementName="RootContainer" Path="ActualWidth" />
                <Binding ElementName="SomeContainer" Path="ActualWidth" />
            </MultiBinding>
        </Canvas.Left>
        <Canvas.Top>
            <MultiBinding Converter="{StaticResource CenterValueConverter}">
                <Binding ElementName="RootContainer" Path="ActualHeight" />
                <Binding ElementName="SomeContainer" Path="ActualHeight" />
            </MultiBinding>
        </Canvas.Top>

        <ItemsControl ItemsSource="{Binding SomeChilds}" Name="ItemsControl">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Left" Value="{Binding Left}" />
                    <Setter Property="Canvas.Top" Value="{Binding Top}" />
                </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ConventionBasedDataTemplateSelector}"
                                MouseLeftButtonDown="OnMouseLeftButtonDown" PreviewMouseLeftButtonUp="OnPreviewMouseLeftButtonUp" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <!-- Some other controls over here -->
    </Canvas>
</Canvas>

And this converter:

public class CenterValueConverter : IMultiValueConverter
{

    public object Convert(object[] values,
        Type targetType,
        object parameter,
        CultureInfo culture)
    {
        if (values == null || values.Length != 2)
        {
            return null;
        }

        double totalWidth = (double)values[0];
        double size = (double)values[1];
        return totalWidth / 2 - (size / 2);
    }

    public object[] ConvertBack(object value,
        Type[] targetTypes,
        object parameter,
        CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

The issue is that the second value(SomeContainer.ActualWidth/ActualHeight) always comes with a value of 0(even when I've some real elements it).

Any idea why and how to fix this? Or another XAML way of centering SomeContainer inside of the RootContainer ?

EDIT Maybe some additional informations on why I planned to use so much Canvas.

  • The RootContainer one is because SomeContainer will have transformation(scale for zooming) and translation for panning
  • The SomeContainer could be something different I guess
  • The Canvas inside the ItemControls is because each elements will be positioned as a very specific place.
J4N
  • 19,480
  • 39
  • 187
  • 340
  • What do you attempt to achieve? I don’t see any reason you need nested-canvas. – Frank Apr 26 '21 at 17:18
  • What did you expect? You are not setting a size for the Canvas SomeContainer. And by default, inside another Canvas will have a size of zero. – EldHasp Apr 26 '21 at 17:38
  • @EldHasp I was thinking the canva would fit it's content – J4N Apr 26 '21 at 17:45
  • Nope. Use a Grid. – Andy Apr 26 '21 at 18:35
  • Explain the logic you want to implement. It is difficult to understand from your code. Maybe you need to center the ItemsControl? – EldHasp Apr 26 '21 at 18:46
  • @EldHasp I need to have the SomeContainer centered horizontally and vertically. Also, in the future, I will have to allow to implement some panning and zooming(in which case the code that centers everything will be disabled). – J4N Apr 26 '21 at 19:02
  • You are explaining the obvious. Explain not how you want to implement something, but what you are implementing. In your code, Canvas contains only ItemsControl. And centering a Canvas with one element looks like complete nonsense. Also keep in mind that, unlike other elements (Grid, for example), the size of the Canvas does not react in any way to its content. – EldHasp Apr 26 '21 at 19:13
  • The key part of my explanation is that in the future(like in the next month), I will have to implement zooming(mousewheel) where the goal was just to transform the child canvas and to implement panning(drag) and the goal was just to change top/left coordinates. Then the "auto" mode could be enabled, centering everything again. – J4N Apr 27 '21 at 04:27

1 Answers1

0

I guess your ActualHeight/ActualWidth are = 0 because you didn't load the window yet. To make these calculous taking in account dimensions of Canvas you may use 'OnContentRendered' event, that will be launched after the window is displayed, so you will have their dimensions appearing. If your Canvas may change dimensions, you may also use SizeChanged event. Even I think it is a bit complicated, and you could just use that XAML code taken from Clemen's answer :

<Canvas Background="Transparent"
        SizeChanged="ViewportSizeChanged"
        MouseLeftButtonDown="ViewportMouseLeftButtonDown"
        MouseLeftButtonUp="ViewportMouseLeftButtonUp"
        MouseMove="ViewportMouseMove"
        MouseWheel="ViewportMouseWheel">

    <Canvas x:Name="canvas" Width="1000" Height="600">
        <Canvas.RenderTransform>
            <MatrixTransform x:Name="transform"/>
        </Canvas.RenderTransform>

        <Ellipse Fill="Red" Width="100" Height="100" Canvas.Left="100" Canvas.Top="100"/>
        <Ellipse Fill="Green" Width="100" Height="100" Canvas.Right="100" Canvas.Bottom="100"/>
    </Canvas>
</Canvas>
Siegfried.V
  • 1,508
  • 1
  • 16
  • 34
  • Canvas inside Canvas will be zero-sized unless explicitly set. All other elements will also have the same if they have no filling. Inside Canvas, Stretch values for HorizontalAlignment and VerticalAlignment don't work. Canvas is also unresponsive to its content. Canvas can be zero-sized, and content whatever size. – EldHasp Apr 26 '21 at 18:53