Binding detached issue when using a ControlTemplate - wpf

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.

Related

Editing the property of an element within the ControlTemplate in WPF

I have some radio buttons that I'm building a custom control template for. Image of the buttons:
In the control template, each radio button will have a textblock with its name and another textblock below it to indicate if it's unavailable.
I want the "Unavailable" text to be visible ONLY when the button is NOT enabled. When the radio button is ENABLED, the "Unavailable" textblock should be collapsed.
Here is the simplified view.xaml for the buttons:
<RadioButton Name="one"
IsEnabled="{Binding One_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="two"
IsEnabled="{Binding Two_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="three"
IsEnabled="{Binding Three_isAvailable}"
Style="{StaticResource RadioButtonTheme}"/>
Here is the simplified version of the styling I have so far (RadioButtonTheme.xaml):
<ResourceDictionary>
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
So I've tried setting a couple things:
I set a visiblitity property on the radio button on the view.xaml. I then binded that visibility to the "UnavailableTextBlock" in the radiobuttontheme.xaml and set the rest of the template visiblity to "Visible." I thought that I can leave the template visible except for one element of it. I now don't think that's possible.
I tried directly binding the "UnavailableTextBlock" to the IsEnabled property of the radiobutton, and ran it through a BoolToVisiblityConverter.
<TextBlock Name="UnavailableTextBlock"
Visibility="{TemplateBinding Property=IsEnabled, Converter={StaticResource BoolToVisConverter}}">
However, I can't seem to get my converter to work inside of the ResourceDictionary. The program will crash with the error: "Cannot find resource named 'BoolToVisConverter'. Resource names are case sensitive"
I have this converter working across my other xaml files since I added it to my <Application.Resources> in the app.xaml. Do I need to link my Resource dictionary to the converter? How do I do that? <ResourceDictionary.Resources> didn't seem to work for me.
I tried adding a datatrigger to the "UnavailableTextBlock" as below:
<TextBlock Name="UnavailableTextBlock"....>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{TemplateBinding Property=IsEnabled}" Value="false">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
However, I get an error saying: '"IsEnabled" member is not valid because it does not have a qualifying type name.'
I'm guessing that it's referencing the IsEnabled property of the TextBlock and not of the radio button? Although I'm not too sure. I'm still learning WPF.
Thanks for all your help in advance!
If I understand correctly what you want to implement, then you need to use the control template trigger.
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="UnavailableTextBlock" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF set default Visibility value when editing

I have Visibility bound to a bool, which works perfectly. However when editing the page the Border is not visible. I have to delete the Visibility Binding, make my changes and redo the Visibility Binding.
I'm pretty sure I saw there is a way to set a "editing default", but I cannot find that link anymore (or remember what it was called). Can someone explain how to set the default to visible so I can see it whilst editing, but not affect it's operation at runtime?
<Border Grid.Column="2" BorderBrush="HotPink" BorderThickness="2" MinHeight="100" MinWidth="100"
Visibility="{Binding ElementName=GenerateWorkOrders, Path=IsChecked, Converter={StaticResource booleanToVisibility}, UpdateSourceTrigger=PropertyChanged}">
<Label Content="Not Visible While Editing"/>
</Border>
The problem is that the default value of IsChecked of GenerateWorkOrders CheckBox is false
If IsChecked have Binding, you can use FallbackValue:
<CheckBox x:Name="GenerateWorkOrders" IsChecked="{Binding SomeProperty, FallbackValue=True}" />
Another way is to avoid the binding, you can use the DesignerProperties.IsInDesignMode Attached Properties that indicate if you in design mode (More inforamtion).
You can use this property in behavior, or in XAML only approach:
<Border Grid.Column="2" BorderBrush="HotPink" BorderThickness="2" MinHeight="100" MinWidth="100">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Visibility" Value="{Binding ElementName=GenerateWorkOrders, Path=IsChecked, Converter={StaticResource booleanToVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(componentModel:DesignerProperties.IsInDesignMode)}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Label Content="Not Visible While Editing"/>
</Border>

Binding the background colour of a control using a trigger in WPF/XAML

Okay, first off I have no experience of WPF whatsoever so please bear with me and apologies if my terminology is a little wayward... ;)
The following code snippet is part of a WPF application that I have inherited. The trigger governs whether mandatory fields on a particular form are highlighted or not. The code works but the highlighting seems to apply to the control and the border (??) which contains it.
<ItemsControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework"
x:Class="company.product.Jobs.JobParametersEditor"
IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel MinHeight="30">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"
DockPanel.Dock="Left"
VerticalAlignment="Center"
MinWidth="120"
Margin="6,0" />
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background"
Value="{x:Null}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}"
Value="False">
<Setter Property="Background"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentControl cal:View.Model="{Binding ValueEditor}"
ToolTip="{Binding ToolTip}"
IsTabStop="False"
MinHeight="19"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
</Border>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The result is a bit clunky so I would like to restrict the highlighting to the control only but I can't figure out how to do it. I've tried moving the trigger so that it applies to the ContentControl instead of the Border but that didn't work and fiddling about with border margins, padding and thickness hasn't had any effect either.
Could anybody enlighten me as to how to accomplish this?

ComboBox highlighting

I have a problem with my combobox I'm trying to customize. It is in a UserControl, and i want its BorderBrush property to change from Transparent to White when the mouse is over it (fade in/out would be bonus).
But I can't seem to get the proper Trigger syntax to do so... now I feel confused and I'm probably missing something obvious here.
Here is the combobox in question:
<ComboBox x:Name="comboEmiCategories" ItemsSource="{Binding}" Background="Transparent" Height="15px" Width="30px" BorderBrush="Transparent" Padding="-2">
<ComboBox.Resources>
<sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">0</sys:Double>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="UniformToFill" Height="15px" Width="30px" Margin="0" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Template>
<ControlTemplate>
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.IsMouseOver" Value="True">
<Setter Property="ComboBox.BorderBrush" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ComboBox.Template>
</ComboBox>
The trigger isn't working, even worse actually, if i don't comment out the whole ComboBox.Template part, the control disappears.
The main goal would be to have a ComboBox that stacks images and allows the user to select one out of a list, with nothing else than those images shown.
Thanks.
EDIT:
Mario's solution of putting it within a style works, but is it the only way to do this?
Try to place the below xaml within the Resources section of your window/usercontrol.
<Style x:Name="cbStyle" TargetType="ComboBox">
<Setter Property="BorderBrush" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
Of course you must refer this style in the ComboBox element. Also cut anything within the ControlTemplate, which is useless.
EDIT: your ComboBox section should look as follows:
<ComboBox x:Name="comboEmiCategories" ItemsSource="{Binding}" Height="15px" Width="30px" Style="{StaticResource cbStyle}" Padding="-2">
<ComboBox.Resources>
<sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">0</sys:Double>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="UniformToFill" Height="15px" Width="30px" Margin="0" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Also put the Style declatarion in your UserControl.

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

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).

Resources