1

I am a new to using WPF but very much like the idea of writing view code in xaml and backing code in a view-model. What I would want to do is extend use of the Canvas by associating it with a status bar that displays status text based on the contents of the Canvas and the mouse position (the stylized code below doesn't include this).

My approach has been to create a UserControl that contains the Canvas and put a ContentPresenter inside it, per https://www.codeproject.com/Articles/82464/How-to-Embed-Arbitrary-Content-in-a-WPF-Control.

I have two problems to solve: 1) What do I need to do to allow more than one child control in the same way that the Canvas allows more than one child control? 2) How do I access properties of the Canvas, such as Canvas.Left, from the main window code?

Thanks in advance for any suggestions you may have.

UserControl xaml code, UserControl code behind, and main window xaml code:

<UserControl x:Class="SO.CanvasUserControl"
             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" 
             xmlns:local="clr-namespace:SO"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <UserControl.Template>
    <ControlTemplate TargetType="{x:Type local:CanvasUserControl}">
      <Canvas Width="200" Height="100" Background="Green">
        <ContentPresenter/>
      </Canvas>
    </ControlTemplate>
  </UserControl.Template>
</UserControl>

Code behind:

  public partial class CanvasUserControl : UserControl
  {
    public CanvasUserControl()
    {
      InitializeComponent();
    }
  }

Main window:

<Window x:Class="SO.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SO"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>

    <!--   works as expected
      <Canvas Width="200" Height="100" Background="Green">
      <Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/>
      <Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Red"/>
    </Canvas>
  -->

    <!-- works as expected
     <Canvas Width="200" Height="100" Background="Green" x:Name="MyCanvas">
      <Line X1="{Binding ElementName=MyCanvas, Path=Left}" Y1="{Binding ElementName=MyCanvas, Path=Top}" X2="{Binding ElementName=MyCanvas, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvas, Path=ActualHeight}" Stroke="Red"/>
      <Line X1="{Binding ElementName=MyCanvas, Path=ActualWidth}" Y1="{Binding ElementName=MyCanvas, Path=Top}" X2="{Binding ElementName=MyCanvas, Path=Left}" Y2="{Binding ElementName=MyCanvas, Path=ActualHeight}" Stroke="Red"/>
    </Canvas>
     -->


    <!-- How do I add more than one child control as nested content for the Canvas?
    <local:CanvasUserControl x:Name="MyCanvasUserControl">
      <Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/>
      <Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Green"/>
    </local:CanvasUserControl>
    -->

    <!-- How do I access dependency properties of the Canvas?
    <local:CanvasUserControl x:Name="MyCanvasUserControl">
      <Line X1="{Binding ElementName=MyCanvasUserControl, Path=Left}" Y1="{Binding ElementName=MyCanvasUserControl, Path=Top}" X2="{Binding ElementName=MyCanvasUserControl, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvasUserControl, Path=ActualHeight}" Stroke="Red"/>
    </local:CanvasUserControl>
    -->

  </Grid>
</Window>
Lars12345
  • 11
  • 3
  • A `UserControl` inherits from `ContentControl`. It has one `Content` property, of type `Object`. I'm not sure what you really want here is a `UserControl`. Do you just want to be able to roll up a bunch of properties for the Canvas into some kind of bundle, and reuse them together? That can be done very easily, but I would do it with a Style instead. – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 20:07
  • What I would like to do is create a re-usable control that acts like a Canvas, but also has a StatusBar beneath it. Each project that I intend to use it in would display whatever it wants to in its list of "Canvas Children", for example two diagonal Lines. In fact I have gotten all this code to work, but get tired of copying an pasting 20-30 lines of xaml as well as ViewModel code which consumes a mouse location and returns status bar text. From my background reading on StackOverflow as well as other sites the UserControl seems the way to go. – Lars12345 Mar 01 '17 at 20:55
  • Define "whatever it wants to". – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 20:59
  • Also, where is it getting the mouse location from? Does it update on mousemove events over itself? – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 21:05
  • Pretty much anything that you would ever want to put on a Canvas. For the first application I had to display a circle, a horizontal line, and a vertical line. For the second one it was an item list that drew labels at particular locations. For the current application it is two item lists of rectangles. (Thanks by way for your responses). – Lars12345 Mar 01 '17 at 21:10
  • 1
    If you want to just toss in random stuff without any extra XAML noise, you want a subclass of `ItemsControl` which has a `Canvas` for an `ItemsPanel`. If you want to add a statusbar to it, you'll want to be replacing the control's `Template` with something that adds the statusbar. All of this could be done in a Style, so far, without even writing your own subclass. The only reason to write your own subclass is to expose the `Canvas`'s properties to consumers (e.g., the XAML in `MainWIndow` that's adding the `Line` objects). You'll have to give your subclass either one dependency... – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 21:15
  • ...property that exposes the `Canvas` (quicker, but a little clunky), or a bunch of them, for Left, Top, etc. But then you've got another problem: I think those'll be relative to the Canvas's immediate parent, but the Canvas's immediate parent will now be your ItemsControl subclass. – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 21:17
  • For the mouse location I created a class, call it CustomCanvas, which inherits from Canvas and overrode OnMouseMove. In turn OnMouseMove sets a dependency property on CustomCanvas that is propagated to a view-model. And this all seems to work. But what I have not been able do is create a "compound control" that acts like a Canvas and has arbitrary children (the diagonal lines in the xaml code). – Lars12345 Mar 01 '17 at 21:19
  • 1
    I think the ItemsControl is the way to go. Unfortunately I threw a bunch of Etruscan hieroglyphics at you about templates and itemspanels and now I have to go home in a few minutes. [Here's an answer that's very close to your case](http://stackoverflow.com/a/3235958/424129), though; the `Template` is what'll be used to display your control. – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 21:24
  • The key feature of `ItemsControl` for your purposes is you can give it multiple immediate children (of any type, too), unlike `UserControl`. And then by setting the `ItemsPanel` property (see the answer I linked) you can make it display the children in a `Canvas`. In the `Template`, think of `ItemsPresenter` as a "placeholder" for the items content, and you can toss in anything else you like around it. – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 21:27
  • Lastly, in WPF, we define a control class (forget UserControl, that's a special case) such as this one as a plan C# class inheriting from `Control` or some `Control` subclass. We then to to XAML to define an "implicit style" that XAML uses to fill in the control's visual aspects. A style with a `TargetType` but no `x:Key` attribute is an implicit style: ` – 15ee8f99-57ff-4f92-890c-b56153 Mar 01 '17 at 21:29
  • I will go ahead and explore your idea of subclassing the ItemsControl. Any details you could give on how to expose the CustomCanvas as a single dependency property would be appreciated. – Lars12345 Mar 01 '17 at 21:35
  • Why don't you just extend Canvas instead of UserControl? – Karmacon Mar 01 '17 at 23:42
  • @Karmacon I'd rather, but it's not a subclass of `Control`, so no `Template`. – 15ee8f99-57ff-4f92-890c-b56153 Mar 02 '17 at 13:56

2 Answers2

1

Maybe I'm not fully understanding your problem, but it sounds like you just want to have a Canvas that has a predefined status bar inside of it. You can do this very easily by extending Canvas instead of UserControl. Here's a custom component that extends Canvas and has a status bar.

<Canvas x:Class="SO.CanvasUserControl"
             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" 
             xmlns:local="clr-namespace:SO"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Border Width="525" Height="30" Background="Black" Canvas.Bottom="0">
        <TextBlock Foreground="White" Text="Hello world" FontSize="16" />
    </Border>
</Canvas>

Now add it to your main window and assign any children you like along with it. You will see the status bar is displayed along with all the children. Since the component extends Canvas, you can add as many children as you want and you can bind to Canvas dependency properties.

<Window x:Class="SO.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SO"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <local:CanvasUserControl x:Name="MyCanvasUserControl" >
        <Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/>
        <Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Green"/>
        <Line X1="{Binding ElementName=MyCanvasUserControl, Path=Left}" Y1="{Binding ElementName=MyCanvasUserControl, Path=Top}" X2="{Binding ElementName=MyCanvasUserControl, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvasUserControl, Path=ActualHeight}" Stroke="Red"/>
    </local:CanvasUserControl>
</Window>
Karmacon
  • 3,128
  • 1
  • 18
  • 20
1

Just wanted to add that the MouseMove issue in the https://stackoverflow.com/a/42558704/9916025 answer above is due to the Background color not being set for the Canvas.

So to solve the problem with MouseMove you should add


<ItemsPanelTemplate>
    <local:CustomCanvas Background="Transparent"
                />
</ItemsPanelTemplate>