views:

1008

answers:

1

Hi,

i have to develop a wpf control which shall have the same behaviour as the well known border. The shape of the control shall be the new part. Every definable closed path shall be used to define the appearence of the control.

I need help to achieve this. Currently i have no idea how to interchange the rectangle(??) with the closed path.

Any help would be highly appreciated.

+2  A: 

Hey Scott.

Edit Here goes direct answer to your question. We will write a ContentControl derived class, with very flexible form of border. Basis for this idea lies in OpacityMask.

If you would like to know more about this approach take a look on example from Chris Cavanagh's solution for rounded corners.

Step 1. Create custom control FreeFormContentControl:

FreeFormContentControl.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace WpfApplication5
{
  public class FreeFormContentControl : ContentControl
  {
    public Geometry FormGeometry
    {
      get { return (Geometry)GetValue(FormGeometryProperty); }
      set { SetValue(FormGeometryProperty, value); }
    }

    public static readonly DependencyProperty FormGeometryProperty =
      DependencyProperty.Register("FormGeometry", typeof(Geometry), typeof(FreeFormContentControl), new UIPropertyMetadata(null));

    static FreeFormContentControl()
    {
      DefaultStyleKeyProperty.OverrideMetadata(
        typeof(FreeFormContentControl),
        new FrameworkPropertyMetadata(typeof(FreeFormContentControl))
        );
    }
  }
}

Themes\Generic.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApplication5">
  <Style TargetType="{x:Type local:FreeFormContentControl}">
    <Setter Property="FormGeometry"
            Value="M0,0 L1,0 1,1 0,1z" />
    <Setter Property="Background"
            Value="Black" />
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:FreeFormContentControl}">
          <Grid>
            <Path Name="mask"
                  Data="{TemplateBinding FormGeometry}"
                  Fill="{TemplateBinding Background}" />
            <Grid>
              <Grid.OpacityMask>
                <VisualBrush Visual="{Binding ElementName=mask}" />
              </Grid.OpacityMask>
              <ContentPresenter />
            </Grid>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

More reading on custom controls can be found on CodeProject.

Step 2. Usage. Now you can place any content inside this control. Its default shape is rectangle. So the following code will result in regular StackPanel UI:

<Window x:Class="WpfApplication5.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cc="clr-namespace:WpfApplication5"
        Title="Window1"
        Height="300"
        Width="300">
  <Grid>
    <cc:FreeFormContentControl>
      <StackPanel>
        <Button Content="Any" />
        <Button Content="Content" />
        <TextBlock Text="Goes" />
        <TextBox Text="Here" />
      </StackPanel>
    </cc:FreeFormContentControl>
  </Grid>
</Window>

But if you define custom FormGeometry you'll get custom shape. For example, the following form geometry presents inner controls inside a diamond:

<cc:FreeFormContentControl FormGeometry="M0,0.5 L0.5,0 1,0.5 0.5,1z">

To read more about geometry definition from XAML, read corresponding section on MSDN: Path Markup Syntax.

The last thing to mention here, is that you don't have to specify or calculate concrete pixel values of your FormGeomtry. Grid makes this trick possible. So think of it as of percentage. I.e. 1 == full width or height. 0.5 == half of available width/hight and so on.

Hope this helps.

Anvaka
Hi Anvaka, thanks for the answer.I think i cannot use your solution because only the visual part is affected. I need a solution which supports the ContentPresenter feature.
Scott Olson
Indeed you can. Wrap this code into custom control. Derive it from ContentControl. Expose one dependency property called "BorderGeometry". And in the control's template bind "mask" Path to it. I.e. you'll have something like this in your template: <Path Name="mask" Data="{TemplateBinding BorderGeometry}"/>.
Anvaka
I Anvaka, thanks again. I understand what you mean and im sure it works. "Costum Control" and "mask" are the right keyword im looking for.. Unfortunately i have never wrote a custom control before and thats why i have to read some theoretical stuff now.(maybe you have simple code sample ??)CU Scott
Scott Olson
you make my day !! good work
Scott Olson
I'm glad I could help :). Cheers
Anvaka