views:

191

answers:

3

I tried to create my own Border class and then insert it in my controls but then it seems I cannot assign names to everything inside the borders:

..
<my:ElementBorder>
        <StackPanel Name="ifBlock" Background="#E0E8FF" />
</my:ElementBorder> 
..

How can I get around this? Can I use templating somehow for that?

EDIT: Sorry that I was unclear. Yes I subclassed Border with my own XAML file and got this compiler error with the code above:

Error 2 Cannot set Name attribute value 'ifBlock' on element 'StackPanel'. 'StackPanel' is under the scope of element 'ElementBorder', which already had a name registered when it was defined in another scope.

The contents of my ElementBorder class aren't very interesting but I'll post it anyway:

<Border x:Class="DVPE.ElementBorder"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    BorderThickness="4" 
    CornerRadius="4">
</Border>
A: 

Name scopes are found by searching the logical tree.

If you rolled your own Border (as opposed to subclassing the Border class), name scope problems can be caused by:

  • Failing to call AddLogicalChild
  • Not correctly overriding the LogicalChildren enumeration

On the other hand, if you subclassed the existing Border (or any Decorator) it would handle the logical tree.

This can also be caused if an explicit NameScope is set on your border. In that case, the name would be assigned but FindName won't find it at the Window/UserContol/template level.

Posting the relevant sections of your ElementBorder class might help us give you a better answer.

Ray Burns
Thanks for answering. I edited my question to give you more information.
codymanix
A: 

This is known as Namescope problem Check here for more details: http://dotnet.dzone.com/news/c-and-wpf-namescope-my-name-is

viky
Thanks for the link but Iam still not sure how to solve my problem. The article suggested calling container.RegisterName("myname", button), but how can it be done in xaml so that it generates a variable "myname"?
codymanix
You first need to determine the applied template, by getting the Template property value of the control where the template is applied. Then, you call the template version of FindName, passing the control where the template was applied as the second parameter.
viky
+2  A: 

By introducing code-behind you have created an additional NameScope. You can remove this extra NameScope at runtime by clearing the NameScope after calling InitializeComponent():

public ElementBorder()
{
  InitializeComponent();
  NameScope.SetNameScope(this, null);
  ...
}

Although this will work, it is not your best solution. You would be better off creating a style that a UserControl.

There are two ways to do this: With a subclass and without.

With a subclass

Create your ElementBorder subclass and override the default style. Do not call InitializeComponent():

public class ElementBorder
{
  static ElementBorder
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(ElementBorder), new FrameworkPropertyMetadata(typeof(ElementBorer)));
  }
  // any additional implementation
}

In your xaml file, instead of including a tag, create a ResourceDictionary containing a style with the settings you want:

<ResourceDictionary xmlns=... >

  <Style TargeType="{x:Type my:ElementBorder}">
    <Setter Property="BorderThickness" Value="4" />
    ...

Merge this ResourceDictionary into the one defined in app.xaml or themes/Generic.xaml using a <MergeDictionary> tag

Note that you can just put the style in the ResourceDictionary in your app.xaml instead of creating a separate file.

Using this is identical to using your original ElementBorder:

<my:ElementBorder>
  <StackPanel  ...

Without a subclass

This requires less code. Just put a Style in a ResourceDictionary as before, except give it a x:Key and use Border as it's target type:

<ResourceDictionary>

  <Style x:Key="ElementBorderStyle" TargeType="{x:Type Border}">
    <Setter Property="BorderThickness" Value="4" />
    ...

This can be used as follows:

 <Border Style="{StaticResource ElementBorderStyle}">
   <StackPanel ...

If you want all of your borders to have the new style, it is even easier. Just omit the x:Key and use Border as your TargetType:

<ResourceDictionary>

  <Style TargeType="{x:Type Border}">
    <Setter Property="BorderThickness" Value="4" />
    ...

This will cause all borders to get the style, so you can just write:

<Border>
  <StackPanel ...

Answer to additional question asked in comment below

To set the BorderBrush the same as the Background:

  <Style TargeType="{x:Type Border}">
    <Setter Property="BorderBrush" Value="{Binding Background, RelativeSource={RelativeSource Self}}" />
    ...

To set the child's Background to the Border's background: Generally not necessary since as long as child's background color isn't set, the border's background color will show through. The only exception is in negative-Margin or RenderTransform situations where the child doesn't render entirely inside the containing border. In that case you will need to bind the child's Background to the Border's Background. This can't be done in a style applied to the BorderBrush without using an attached property. But if you're ok doing it without a style:

<Border x:Name="myBorder">  <!-- Style applied here -->
  <StackPanel Background="{Binding Background, ElementName=myBorder}" ...

this can also be done with an unnamed border with {Binding Background, RelativeSource={RelativeSource FindAncestor,Border,1}.

If you want to do it entirely in the style, you'll have to add some code. Basically, create a class with an attached property named something like "MyBindingTools.BindChildBackgroundToMyBackground". Add a PropertyChangedCallback so that when that property is set to "true", a binding is created on the child, basically:

BindingOperations.SetBinding(border, BackgroundProperty, new Binding("Background") { Source = this });

In addition you will need to monitor the Border's Child property so that when it changes the binding can be added to the new Child and removed from the old Child (if any).

I wouldn't recommend you do any of this unless you really need to, though. In your specific situation you can probably either bind the child manually or create a template that contains both the Border and the child control. Either of these would be better solutions that creating the attached property as I described.

Ray Burns
codymanix
I'm not understanding you. If you want each control to be a different color, where will you get that color? Data binding? If so, you can put the binding expression in the style. In any case, for your simple case you can set the Border.BorderBrush and child.Background to Brushes.Transparent, which will cause only the Border's background to be visible. This will look very similar to setting all three the same color (the border will be slightly smaller since there will be no stroke).
Ray Burns
Are you saying the deleting your ElementBorder class entirely and using <Border Style="{StaticResource ElementBorderStyle}"> causes the name scope error (ie it affects the StackPanel's name), but it works fine if you do exactly the same but leave off the Style= attribute? Or am I misunderstanding you here as well?
Ray Burns
codymanix
Answered in the question body.
Ray Burns