Error "Specified element is already the logical child of another element"? - wpf

I have a TabControl and each Tab can contain the same UI but with different data. In any tab the user can click on a button and bring up a popup. This sets a Style property to the ViewModel telling it what style to use for the popup UI. The Style gets bound to a custom DependecyProperty that is attached to a custom PopupUserControl. My problem is that when the a 2nd copy of the popup gets opened in another tab, I get the following error (regardless of what Style is applied):
Specified element is already the
logical child of another element.
Disconnect it first.
ButtonClick command:
MyViewModel vm = ((Button)sender).DataContext as MyViewModel;
if (vm != null)
{
Style popupStyle = (Style)Application.Current.FindResource("SomePopupStyle");
vm.EditPanelStyle= popupStyle ;
}
This triggers a PropertyChange event on the Style
public Style EditPanelStyle
{
get { return _editPanelStyle; }
set
{
if (_editPanelStyle != value)
{
_editPanelStyle = value;
OnPropertyChanged("EditPanelStyle");
}
}
}
Which triggers the OnPropertyChanged event in the ViewModelBase
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
The error occurs at line handler(this, e); in the ViewModelBase
EDIT
The TabItem contains a Canvas and a bunch of panels that can be added/removed/moved/etc. Each Panel has its own resource directory. From within the Panel I can set the PopupStyle just fine and it gets applied without a problem. The Style used within the panels is also defined in the PanelResourceDictionary.
The main difference with the one that is failing and the ones succeeding is the Style is located in different locations.
EDIT #2
Style that is failing - LookupDialog is a custom WPF UserControl
<!-- Popup Style for LookupDialog -->
<Style x:Key="LookupDialogBaseStyle" TargetType="{x:Type localControls:DraggablePanel}" BasedOn="{StaticResource GenericPopupStyle}">
<Setter Property="Canvas.Left" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualWidth, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.25}" />
<Setter Property="Canvas.Top" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualHeight, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.3}" />
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualWidth, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.5}" />
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualHeight, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.4}" />
<!--<Setter Property="localControls:PopupPanel.PopupEnterKeyCommand" Value="{Binding Path=SaveCommand}" />-->
<Setter Property="localControls:PopupPanel.PopupEscapeKeyCommand" Value="{Binding Path=CancelCommand}" />
<Setter Property="Header" Value="{Binding Path=Header}" />
<Setter Property="localControls:PopupPanel.BackgroundOpacity" Value="0" />
<Setter Property="Content">
<Setter.Value>
<localControls:LookupDialog
DataContext="{Binding}"
BorderBrush="{StaticResource DarkColor}" />
</Setter.Value>
</Setter>
</Style>
<!-- Base Style for a Popup (DraggablePanel) -->
<Style x:Key="GenericPopupStyle" TargetType="{x:Type localControls:DraggablePanel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type localControls:DraggablePanel}">
<Border Height="{TemplateBinding Height}" Width="{TemplateBinding Width}">
<DockPanel>
<!-- Header -->
<Border
DockPanel.Dock="Top"
MinHeight="20"
Background="{DynamicResource TabItem_BackgroundBrush_Unselected}"
BorderBrush="{StaticResource DarkColor}"
BorderThickness="1"
CornerRadius="5,5,0,0"
Padding="2,3,2,2"
SnapsToDevicePixels="True"
>
<ContentPresenter x:Name="PART_DraggablePanelHeader" ContentSource="Header" />
</Border>
<!-- Content -->
<Border Background="{StaticResource DefaultBackground}"
BorderBrush="{StaticResource DarkColor}"
BorderThickness="1,0,1,1"
SnapsToDevicePixels="True">
<ContentPresenter ContentSource="Content" />
</Border>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Style that Works:
<!-- Example Popup Style for a Panel -->
<Style x:Key="AgentDesktop_NotesPanelPopupStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Canvas.Left" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualWidth, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.2}" />
<Setter Property="Canvas.Top" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualHeight, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.32}" />
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualWidth, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.6}" />
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type localControls:PopupPanel}},
Path=ActualHeight, Converter={StaticResource PercentToDoubleConverter}, ConverterParameter=.36}" />
<Setter Property="localControls:PopupPanel.PopupEnterKeyCommand" Value="{Binding Path=SaveCommand}" />
<Setter Property="localControls:PopupPanel.PopupEscapeKeyCommand" Value="{Binding Path=HidePopupCommand}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- Control Template removed to make this easier to read, but it's created from standard WPF controls with nothing special -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

My problem was I was setting the Content in my Style, and Content cannot have more then one logical parent. I needed to move that to a Template instead. Since I didn't want to lose the base style, I set the content in the ContentTemplate property of the HeaderedContentControl (DraggablePanel in my code).
+1 to Davy anyways for helping me walk through this.

Make sure you're creating a new tab object and you're not trying to insert the same tab into the tab control a 2nd time. A control can only have 1 parent and it seems like the issue is you're trying to insert a tab into either 2 different containers, or more likely the same tab control twice.

To not read all the above the short answer is: you need to change Content to Template (in your XAML either style or direct declaration of ContentControl).

Related

Binding detached issue when using a ControlTemplate

I have a listbox which shows some labels
and then multiple (theoretically n, practically up to 6) listboxes which each show some values. Each of those n listboxes is its own view/viewmodel.
Now im changing a Property "HasChanged" in my Value-class inside the list box-ViewModel. I have a binding from "HasChanged" to "IsSelected". There is a List of instances of the value-class in each ViewModel.
Additionally, I have a trigger that listens to "IsSelected"-changes.
Part of the trigger is to apply a new control template.
Inside that Template, I define some styling to make the state visible to the user.
Now here is the problem:
I am using "ValidatesOnExceptions", to make the invalidity of values visible to the user.
Here is the code of the value property:
public string Content
{
get
{
if (!string.IsNullOrEmpty(modifiedValue))
return modifiedValue;
return content;
}
set
{
modifiedValue = value;
HasChanged = (modifiedValue != content);
if (!string.IsNullOrEmpty(interpretAs))
Validate();
OnPropertyChanged(nameof(Content));
}
}
Since I'm setting the "HasChanged"-property thus triggering the selection-control template application, the control template sets a new TextBox with a border.
Now when I'm doing the validation, since the textbox-control has been changed, the binding is detached.
I get the following message:
Note: the problem only occurs when altering IsChanged and Validating with an error in the same setter-call.
I have noticed I could maybe do a workaround, using lots of style triggers instead of the control template. But there's still hope.. im also running into focus issues again when I cant use the control template.
This is the XAML of my Grid.Resources:
<ControlTemplate x:Key="SelectedTemplate" TargetType="{x:Type ListBoxItem}">
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="Gray" Background="LightBlue" HorizontalAlignment="Stretch" Padding="{TemplateBinding Padding}" Margin="{TemplateBinding Margin}" >
<TextBox Text="{Binding Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" Margin="0" Padding="0" BorderThickness="0" Background="Transparent" Initialized="TextBox_Initialized" ToolTip="{Binding InterpretAs}" ToolTipService.InitialShowDelay="0"/>
</Border>
</ControlTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=HasChanged}"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Template" Value="{StaticResource SelectedTemplate}" />
<!--<Setter Property="Border.BorderThickness" Value="1"/>
<Setter Property="Border.BorderBrush" Value="Gray"/>
<Setter Property="TextBox.Background" Value="Green"/>
<Setter Property="TextBox.HorizontalAlignment" Value="Stretch"/>-->
</MultiTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="ListBoxDataTemplate">
<TextBox Text="{Binding Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" BorderThickness="0" Margin="0" Padding="0" Background="Transparent" ToolTip="{Binding InterpretAs}" ToolTipService.InitialShowDelay="0" />
</DataTemplate>
And this is the XAML for one of the listboxes:
<ScrollViewer Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="6" Scroll:ScrollSynchronizer.HorizontalScrollGroup="H0" Scroll:ScrollSynchronizer.VerticalScrollGroup="V0" VerticalScrollBarVisibility="Hidden">
<ListBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="6" ItemsSource="{Binding PlatformValues}" ItemTemplate="{StaticResource ListBoxDataTemplate}" PreviewMouseWheel="ListBox_PreviewMouseWheel" SelectionMode="Extended" HorizontalContentAlignment="Stretch" FocusManager.IsFocusScope="True">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding PlatformValues.Count, FallbackValue=0, TargetNullValue=0}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
</ScrollViewer>
I have created a sample.
Try clicking a textbox then removing the last character. So far so good. Now try to set it to a string starting with "x" to trigger validation error, on the same textbox thats already been changed, there will be a red border indicating validation error, thats the desired behavior.
Now set a yet unchanged TextBox directly to "x", the desribed problem will be reproduced. Path to the sample project : LINK REMOVED - SEE ANSWER
Any help on this is appreciated.
The answer is to use a DataTemplate trigger instead of a ControlTemplate
In the DataTemplate I added a border with default background transparent.
<DataTemplate x:Key="ListBoxDataTemplate">
<Border x:Name="ContentTextBoxBorder" BorderThickness="0" BorderBrush="Transparent" Background="Transparent" HorizontalAlignment="Stretch" Margin="-3 0">
<TextBox x:Name="ContentTextBox" Text="{Binding Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" BorderThickness="0" Margin="0" Padding="0" Background="Transparent" ToolTip="{Binding InterpretAs}" ToolTipService.InitialShowDelay="0" />
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}},
Path=IsSelected}" Value="True">
<Setter TargetName="ContentTextBoxBorder" Property="Background" Value="LightGreen"/>
<Setter TargetName="ContentTextBoxBorder" Property="BorderBrush" Value="Gray"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
I set it to the desired color when "IsSelected" changes for an item. Working flawlessly.
To #Il Vic - if you're unable to help, keep your comments.

How can I vary the layout of a UserControl by a Property?

I've made the smallest project I can to demonstrate the problem:
Code behind:
public partial class AxisControl : UserControl
{
public static readonly DependencyProperty LayoutProperty =
DependencyProperty.Register("Layout", typeof(Orientation), typeof(AxisControl),
new PropertyMetadata(Orientation.Horizontal));
public Orientation Layout
{
get { return (Orientation)GetValue(LayoutProperty); }
set { SetValue(LayoutProperty, value); }
}
public AxisControl()
{
InitializeComponent();
}
}
Xaml:
<UserControl.Resources>
<ContentControl x:Key="horizontalLayout" Height="60">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource AncestorType=UserControl}}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</ContentControl>
<ContentControl x:Key="verticalLayout" Width="60">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource AncestorType=UserControl}}" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</ContentControl>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Setters>
<Setter Property="Content" Value="{StaticResource horizontalLayout}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Layout, ElementName=root}" Value="Vertical">
<Setter Property="Content" Value="{StaticResource verticalLayout}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Edit: Xaml now includes the elements I want to arrange in the UserControl.
MainWindow Xaml:
<Grid>
<local:AxisControl Layout="Vertical"/>
</Grid>
The idea is to set the layout of the UserControl according to its Layout property so I put both layouts in static resources and made a Style to set the Content to the one I want according to Layout which is of type Orientation.
Edit: I want the Content to include elements arranged in a different order according to the Orientation.
The UserControl displays correctly but there's an error in the output window that concerns me:
Cannot find source for binding with reference 'RelativeSource
FindAncestor, AncestorType='System.Windows.Controls.UserControl',
AncestorLevel='1''. BindingExpression:Path=Layout; DataItem=null;
target element is 'TextBlock' (Name=''); target property is 'Text'
(type 'String')
Does this mean it's trying to do a Binding before it's in the visual tree possibly from the Trigger?
Note the use of RelativeSource and ElementName in the Bindings as it's incorrect to set the DataContext at the root of a UserControl because it breaks the DataContext inheritance.
What am I doing wrong and how can I get rid of the error?
Inspired by Clemens comment and further research I realized I needed a ControlTemplate resource for each layout and not resources containing instances of the elements themselves.
<UserControl.Resources>
<ControlTemplate x:Key="horizontalLayout">
<Border Height="60" Background="LightBlue">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Border>
</ControlTemplate>
<ControlTemplate x:Key="verticalLayout" TargetType="UserControl">
<Border Width="60" Background="LightBlue">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Border>
</ControlTemplate>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Setters>
<Setter Property="Template" Value="{StaticResource horizontalLayout}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Layout, ElementName=root}" Value="Vertical">
<Setter Property="Template" Value="{StaticResource verticalLayout}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Layout = Horizontal
Layout = Vertical

Button control template and DataTriggers

In the application on which I'm working I have couple of items which I monitor. Every item has several states (on, off, change).
States are saved in model and DataContext of window is set to it.
Every button DataContext is set to single item in SortableBindingList.
In the XAML I have created Control Template for button and in DataTrigerrs in it.
However, sometimes item only shows for first button, sometimes only for first three buttons (depending on what content I put inside DataTrigger).
When I check in Snoop I see that all three buttons have correct DataContext set and there are no binding errors.
I expect that every button shows it's content depending on value of 'serverstate' property however this does not happen.
What am I doing wrong?
Control Template goes to Window.Resources:
<ControlTemplate x:Key="buttonTemplate" TargetType="Button" >
<Border BorderThickness="1"
BorderBrush="Gray"
Margin="2"
Width="40" Height="35"
>
<Border.Background>
<SolidColorBrush Color="Brown" />
</Border.Background>
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding serverstate}" Value="0">
<Setter Property="Content">
<Setter.Value>
<TextBlock Name="a1" Text="OFF" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding serverstate}" Value="1">
<Setter Property="Content">
<Setter.Value>
<TextBlock Name="a2" Text="CHNG" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding serverstate}" Value="2">
<Setter Property="Content">
<Setter.Value>
<TextBlock Name="a3" Text="ON" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" />
</Setter.Value>
</Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Buttons:
<Button Name="state1"
DataContext="{Binding ActiveServers[0]}"
Template="{StaticResource buttonTemplate}"
/>
<Button Name="state2"
DataContext="{Binding ActiveServers[1]}"
Template="{StaticResource buttonTemplate}"
/>
<Button Name="state3"
DataContext="{Binding ActiveServers[2]}"
Template="{StaticResource buttonTemplate}"
/>
<Button Name="state4"
DataContext="{Binding ActiveServers[3]}"
Template="{StaticResource buttonTemplate}"
/>
<Button Name="state5"
DataContext="{Binding ActiveServers[4]}"
Template="{StaticResource buttonTemplate}"
/>
This is how buttons shows up in window:
Notice how button 4 and 5 completely ignore property value in datacontext.
Model:
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Model()
{
}
private SortableBindingList<Server> _activeServers;
public SortableBindingList<Server> ActiveServers
{
get {
if (_activeServers == null)
{
_activeServers = new SortableBindingList<Server>();
}
return _activeServers;
}
set
{
_activeServers = value;
OnPropertyChanged("serverList");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
I think the problem is, that two buttons cann't use same Content. So each type of Content appears one time for each trigger value. You can avoid this by using ContentTemplate.
So the code should look like this (same for other DataTriggers). It allows you to change DataTemplates to more complicated later.
<DataTrigger Binding="{Binding serverstate}" Value="2">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="ON" VerticalAlignment="Center"
HorizontalAlignment="Center" Foreground="White" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
Try to change Content property, but not creating new TextBlock:
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding serverstate}" Value="0">
<Setter Property="Content" Value="OFF"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding serverstate}" Value="1">
<Setter Property="Content" Value="CHNG"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding serverstate}" Value="2">
<Setter Property="Content" Value="ON"></Setter>
</DataTrigger>
</ControlTemplate.Triggers>

Binding Path Fill to Button Foreground in ContentPresenter

I have a Button Style with a Template containing a ContentPresenter, in which I am attempting to bind the Fill of a Path to the Foreground of a button:
<!-- This is inside the template of a button style -->
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
I also have a Path with no Fill set, that I can reference in the button as the content, like so:
<Button Style="{DynamicResource MyButtonStyle}" Content="{DynamicResource PathIcon}" Foreground="Blue"/>
I would expect the Path inside the button to be blue, but it isn't... it doesn't grab the foreground from the button.
How can I get the Path to bind to the color of the button?
Thank you!
P.S.:
If I put a hardcoded color in the Value (i.e. Value="Red"), the Path inside the button is red... so I know that works...
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="Red"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
Edit:
Here is the complete Style and ControlTemplate:
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource White_Brush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid" Background="Transparent">
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Should affect Text as well as Paths in the Content property of the button! -->
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Okay, let's order:
it doesn't grab the foreground from the button.
In styles this construction:
RelativeSource={RelativeSource AncestorType=Button}
will not work, because the Style is just the collection of setters, he does not know about control, are there, specifically about the content of the visual tree. Because RelativeSource should refer to the items above in the visual tree. For this purpose, usually using DataTemplate or ControlTemplate.
If I put a hardcoded color in the Value (i.e. Value="Red")
Yes, in this case, will be working, and always better to create the design of the form:
<SolidColorBrush x:Key="MyButtonColor" Color="Blue" />
And use it for control, like Button:
<Button Background="{StaticResource MyButtonColor}" ... />
and in Style or elsewhere:
<Setter Property="Fill" Value="{StaticResource MyButtonColor}" />
That is, it is better not to depend on the element parameters (background color, etc.) located in a visual tree, because it can:
May move to another panel (Grid, StackPanel) or UserControl
May leave from the project
And brushes in the as resources will always be in one place, changing them in this place, all the elements of their pick up. Also colors can be stored in a special data model that does not depend on the specific technical implementations (resources, variables) in which the data can come from an external source, such as the project/config settings.
If possible, it is better to avoid the use of dynamic resources due to unnecessary use of system perfomance (and in some cases memory leaks), in your cases they are not needed.
Dynamic resources are usually explicitly defined for SolidColorBrush and another species brushes, because by default they are frozen, and they not recommended changed because of the above mentioned reasons (memory leaks). More information can be found here:
Freezable Objects Overview on MSDN
Edit
As I understand it, you want to make universal Style for Button to make the contents of Path or Text (in the case of simultaneous use will be easier). As I have already mentioned above, RelativeSource should be around ControlTemplate, therefore, the Path will be in the Grid with the ContentPresenter.
To style knew, which is provided for the text or for the path, to the Tag (optional property) indicates two properties: OnlyText or OnlyPath.
To set the data for the Path, I've created a attached dependency property, and prescribed it in the ControlTemplate.
Below is a complete example:
XAML
<Window x:Class="ButtonPathHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ButtonPathHelp"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="Green_Brush" Color="Green" />
<SolidColorBrush x:Key="Black_Brush" Color="Black" />
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource Green_Brush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid">
<ContentPresenter x:Name="MyContent"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}" />
<Path x:Name="MyPath"
SnapsToDevicePixels="True"
Width="20"
Height="18"
Stretch="Fill"
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:MyDependencyClass.DataForPath)}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
<Trigger Property="Tag" Value="OnlyText">
<Setter TargetName="MyPath" Property="Visibility" Value="Collapsed" />
<Setter TargetName="MyContent" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="Tag" Value="OnlyPath">
<Setter TargetName="MyPath" Property="Visibility" Value="Visible" />
<Setter TargetName="MyContent" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<WrapPanel>
<WrapPanel.Resources>
<sys:String x:Key="Save">
F1 M 20.5833,20.5833L 55.4167,20.5833L 55.4167,55.4167L 45.9167,55.4167L 45.9167,44.3333L 30.0833,44.3333L 30.0833,
55.4167L 20.5833,55.4167L 20.5833,20.5833 Z M 33.25,55.4167L 33.25,50.6667L 39.5833,50.6667L 39.5833,55.4167L 33.25,
55.4167 Z M 26.9167,23.75L 26.9167,33.25L 49.0833,33.25L 49.0833,23.75L 26.9167,23.75 Z
</sys:String>
<sys:String x:Key="Search">
F1 M 23.4454,49.2637L 31.7739,41.1598C 30.6986,39.2983 30.4792,37.1377 30.4792,34.8333C 30.4792,27.8377 35.7544,
22.1667 42.75,22.1667C 49.7456,22.1667 55.4167,27.8377 55.4167,34.8333C 55.4167,41.8289 49.7456,47.1042 42.75,
47.1042C 40.5639,47.1042 38.5072,46.9462 36.7125,45.9713L 28.3196,54.1379C 27.0829,55.3746 24.6821,55.3746 23.4454,
54.1379C 22.2088,52.9013 22.2088,50.5004 23.4454,49.2637 Z M 42.75,26.9167C 38.3777,26.9167 34.8333,30.4611 34.8333,
34.8333C 34.8333,39.2056 38.3777,42.75 42.75,42.75C 47.1222,42.75 50.6667,39.2056 50.6667,34.8333C 50.6667,
30.4611 47.1222,26.9167 42.75,26.9167 Z
</sys:String>
</WrapPanel.Resources>
<Button Name="SaveButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Save}"
Margin="10" />
<Button Name="JustText"
Style="{StaticResource Button_Style}"
Tag="OnlyText"
Content="Just Text"
Margin="10" />
<Button Name="SearchButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Search}"
Margin="10" />
</WrapPanel>
</Window>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MyDependencyClass : DependencyObject
{
#region IsCheckedOnDataProperty
public static readonly DependencyProperty DataForPathProperty;
public static void SetDataForPath(DependencyObject DepObject, string value)
{
DepObject.SetValue(DataForPathProperty, value);
}
public static string GetDataForPath(DependencyObject DepObject)
{
return (string)DepObject.GetValue(DataForPathProperty);
}
#endregion
static MyDependencyClass()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(String.Empty);
DataForPathProperty = DependencyProperty.RegisterAttached("DataForPath",
typeof(string),
typeof(MyDependencyClass),
MyPropertyMetadata);
}
}
Note: In the Style I have not used TemplateBinding for attached property, because TemplateBinding doesn’t work outside a template or outside its VisualTree property, so you can’t even use TemplateBinding inside a template’s trigger. Therefore, we must use the construction {RelativeSource TemplatedParent} and a Path equal to the dependency property whose value you want to retrieve.
Output
To download the entire example please follow this link.
I stumbled across simillar problem but was wondering how to get to the 'Foreground Colour' of the Button in its DISABLED state (to have correct colour of my drawing). Here is a finally simple sollution. No templates, No styles, no code, nothing at all. Just the right relative binding :-) :
<StackPanel Orientation="Horizontal">
<Button Height="22" IsEnabled="False">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="90"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
<Button Height="22" IsEnabled="True">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="180"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
</StackPanel>

Moving ListBoxItems around a Canvas?

I'm working on dragging objects around a Canvas, which are encapsulated in ListBoxItems -- the effect being to create a simple pseudo desktop.
I have a ListBox with a Canvas as the ItemsPanelTempalte, so that the ListBoxItems can appear anywhere on screen:
<ListBox ItemsSource="{Binding Windows}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I have a Style to define how the ListBoxItems should appear:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Canvas.Left" Value="{Binding Left, Mode=TwoWay}" />
<Setter Property="Canvas.Top" Value="{Binding Top, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<local:PseudoWindowContainer Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The "PseudoWindowContainer" extends from the ContentControl and has its own Style applied to make it look like a dialog box (title bar, close button, etc...). Here is a chunk of it:
<Style TargetType="{x:Type local:PseudoWindowContainer}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Width" Value="{Binding Width, Mode=TwoWay}" />
<Setter Property="Height" Value="{Binding Height, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PseudoWindowContainer}">
<Grid Name="LayoutRoot" Background="White">
<!-- ... snip ... -->
<Border Name="PART_TitleBar" Grid.Row="0" Background="LightGray" CornerRadius="2,2,0,0" VerticalAlignment="Stretch" Cursor="Hand" />
<TextBlock Name="TitleBar_Caption" Text="{Binding DisplayName}" Grid.Row="0" Background="Transparent" Padding="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<Button Name="TitleBar_CloseButton" Command="{Binding CloseCommand}" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,5,5,0" Width="20" Height="20" Cursor="Hand" Background="#FFFF0000" Foreground="#FF212121" />
<!-- ContentPresenter -->
<ContentPresenter Grid.Row="1" />
<!-- ... snip ... -->
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="WindowBorder" Property="Background" Value="Blue" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="WindowBorder" Property="Background" Value="#22000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Inside the PseudoWindowContainer.cs class I create some event handlers to listen for MouseDown/MouseUp/MoveMove events:
public override void OnApplyTemplate()
{
_titleBar = (Border)Template.FindName("PART_TitleBar", this);
if (_titleBar != null)
{
_titleBar.MouseDown += TitleBar_MouseDown;
_titleBar.MouseUp += TitleBar_MouseUp;
}
_grip = (ResizeGrip)Template.FindName("PART_ResizeGrip", this);
if (_grip != null)
{
_grip.MouseLeftButtonDown += ResizeGrip_MouseLeftButtonDown;
_grip.MouseLeftButtonUp += ResizeGrip_MouseLeftButtonUp;
}
base.OnApplyTemplate();
}
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove += TitleBar_MouseMove;
((Border)sender).CaptureMouse();
_windowLocation.X = Left;
_windowLocation.Y = Top;
_clickLocation = this.PointToScreen(Mouse.GetPosition(this));
}
private void TitleBar_MouseUp(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove -= TitleBar_MouseMove;
((Border)sender).ReleaseMouseCapture();
}
private void TitleBar_MouseMove(object sender, MouseEventArgs e)
{
Point currentLocation = this.PointToScreen(Mouse.GetPosition(this));
Left = _windowLocation.X + currentLocation.X - _clickLocation.X;
Top = _windowLocation.Y + currentLocation.Y - _clickLocation.Y;
}
The trouble I run into is the "Left" and "Top" are not defined properties, and updating them to Canvas.SetLeft/SetTop (or GetLeft/GetTop, accordingly) does not update the position on the Canvas.
I have "Left" and "Top" defined in the ViewModel of the controls I place into the ListBoxItems, and are thus subsequently wrapped with a PseudoWindowContainer because of the Template. These values are being honored and the objects do appear in the correct location when the application comes originally.
I believe I need to somehow define "Left" and "Top" in my PseudoWindowContainer (aka: ContentControl) and have them propagate back up to my ViewModel. Is this possible?
Thanks again for any help!
I've found a solution to the problem. Instead of typing it all out again, I will point to the MSDN Forum post that describes what I did:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/d9036b30-bc6e-490e-8f1e-763028a50153
Did you read article by Bea Stollnitz: The power of Styles and Templates in WPF?
That is an example of Listbox where items are drawn at specific coordinates calculated in Converter.

Resources