Bind public string property to TextBox text via content presenter in wpf - c#

I have a custom wpf control that I created in hopes of reusing it over and over to reduce time spent writing, or copying and pasting, a bunch of xaml code. It, seems, pretty simple. It's to be used for a Label and TextBox pair so:
Label here
[ this is the text box (or other control) ]
It's called like so:
<controls:LabeledContentControl Margin="5"
MaxHeight="25"
LabelText="{Binding LabelString}"
Content="{Binding LabelTextBoxTestString, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
or
<controls:LabeledContentControl Margin="5"
LabelText="{Binding LabelString}">
<TextBox Text="{Binding LabelTextBoxTestString,
ValidatesOnNotifyDataErrors=True,
UpdateSourceTrigger=PropertyChanged,
Delay=100}" />
</controls:LabeledContentControl>
The LabeledContentControl extends ContentControl, the c# code doesn't seem relevant as it's dependency properties and their setters. Along with the two constructors similar the ones implemented in the Microsoft reference documents for other controls that extend ContentControl
https://referencesource.microsoft.com/#PresentationFramework/Framework/System/Windows/Controls/HeaderedContentControl.cs
I'm having issues with binding the backing view model property/field set when using the first approach for the labeled content control.
The XAML code for the control is like such:
<Style x:Key="{x:Type controls:LabeledContentControl}" TargetType="{x:Type controls:LabeledContentControl}">
<!-- other setters.... -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:LabeledContentControl}">
<Border Name="OuterBd" BorderThickness="{TemplateBinding BorderThickness}" >
<Border Name="InnerBd" Background="{TemplateBinding BorderBrush}" BorderThickness="0">
<Border Name="Bd" Background="{TemplateBinding Background}" BorderThickness="0">
<DockPanel>
<DockPanel DockPanel.Dock="Top" IsHitTestVisible="False">
<TextBlock x:Name="FieldRequiredInd"
Text="* "
Foreground="Red"
Visibility="Collapsed"
DockPanel.Dock="Left"/>
<TextBlock x:Name="LabelTextBlock"
DockPanel.Dock="Left"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelText, Mode=OneWay}" />
</DockPanel>
<ContentPresenter x:Name="CustomContent" DockPanel.Dock="Bottom"
Content="{TemplateBinding Content}"
ContentTemplateSelector="{StaticResource LabeledContentControlDataTemplateSelector}"
MaxHeight="{TemplateBinding MaxHeight}"/>
</DockPanel>
</Border>
</Border>
</Border>
<ControlTemplate.Triggers>
...
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
The template selector, again super basic, if the content is of type string using GetType a custom datatemplate for strings is used so that they'll be in a text box and editable.
<DataTemplate x:Key="StringDataTemplate" DataType="{x:Type system:String}">
<TextBox>
<TextBox.Text>
<Binding Path="." />
</TextBox.Text>
</TextBox>
</DataTemplate>
<local:LabeledContentControlDataTemplateSelector x:Key="LabeledContentControlDataTemplateSelector"
StringDataTemplate="{StaticResource StringDataTemplate}"/>
How do/can I bind LabelTextBoxTestString correctly so that when I update/type something in the text box generated by the StringDataTemplate that it will update the bound property in the ViewModel? Currently, when I use the first approach, when I type in the text box created by the StringDataTemplate the backing field isn't updated with the new content, but when using the second approach the backing field is updated.

You will need some modifications:
first is the correct Binding in your TextBox.Text to:
<TextBox.Text>
<Binding Path="Content" RelativeSource="{RelativeSource TemplatedParent}" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
then change the Binding of ContentPresenter to:
<ContentPresenter x:Name="CustomContent"
MaxHeight="{TemplateBinding MaxHeight}"
Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:LabeledContentControl}, Path=Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ContentTemplateSelector="{StaticResource LabeledContentControlDataTemplateSelector}"
DockPanel.Dock="Bottom" />
now you can bind your control. And take care, that Mode is set to TwoWay for Content property (default is OneWay), thats why you didn't got a feedback:
<controls:LabeledContentControl Margin="5"
MaxHeight="25"
LabelText="{Binding LabelString}"
Content="{Binding LabelTextBoxTestString, ValidatesOnNotifyDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

Related

Update datagrid's GroupHeader when item property changes

I'm having some difficulty updating a certain binding.
I have a class DeviceList that loads some devices, it inherits from ObservableCollection and is listed as a resource in my XAML:
<local:DeviceList x:Key="Devices" />
Then, I have a CollectionViewSource that uses this devicelist as source, and groups it by a property from the Device:
<CollectionViewSource x:Key="cvsDevices" Source="{StaticResource Devices}" Filter="CollectionViewSource_Filter">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="GroupId" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
A Datagrid binding to this CVS, which has a group header style:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Source={StaticResource cvsDevices}}">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
bla bla
</DataGrid.Columns>
</DataGrid>
And then finally the Group Header style in the resources:
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="White" Foreground="Black">
<Expander.Header>
<StackPanel Orientation="Horizontal" Height="30">
<Border Margin="5" Width="20" Height="20" Background="{Binding Path=Items, Converter={StaticResource DeviceGroupToColorConverter}}" CornerRadius="10" />
<TextBlock VerticalAlignment="Center" Padding="3" Text="{Binding Name, Converter={StaticResource DeviceGroupToGroupTitleConverter}}" />
<TextBlock VerticalAlignment="Center" Padding="3" Text="{Binding ItemCount, Converter={StaticResource ItemCountToStringConverter}}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see, there's a Border there that binds to "Items". This is a property of "CollectionViewGroup": https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.collectionviewgroup?view=netcore-3.1
Basically each of my devices has a property "Connection", and when this property changes, I would like to set the color of this border in the corresponding group header.
The binding works fine the first time, but after that the DeviceGroupToColorConverter isn't called anymore when a connection changes. Device implements INotifyPropertyChanged, but I have no idea how to propagate that event to CollectionViewGroup's Items property. In fact, I have no idea where CollectionViewGroup instances live. I only have access to the CollectionViewSource.
I would like to avoid refreshing the entire DataGrid. I've read that it resets my expanders and also, why refresh the entire datagrid when only a certain group's header should change?
I have solved it by changing the binding to a MultiBinding and adding a binding with a Source set to the DeviceList:
<Border Margin="5" Width="20" Height="20" CornerRadius="10">
<Border.Background>
<MultiBinding Converter="{StaticResource DeviceGroupToColorConverter}">
<Binding Source="{StaticResource Devices}" Path="Devices" />
<Binding Path="Name" />
</MultiBinding>
</Border.Background>
</Border>
the "Devices" property of the DeviceList class is a simple getter that returns "this":
public ObservableCollection<Device> Devices
{
get
{
return this;
}
}
I let the DeviceList listen to any property changes on device, and invoke PropertyChanged on the "Devices" property of DeviceList to pass on this event to the MultiBinding.
I then use the Name binding in the MultiBinding to filter my devices based on the group that they're in. Now I don't need to refresh the whole grid and my performance is good.

C# UWP Template10.Validation Change Style

With "Template10.Validation", I want to change style.
First I made this Style for "validate:ControlWrapper".
<Style TargetType="validate:ControlWrapper">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="validate:ControlWrapper">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Content}" />
<ItemsControl ItemsSource="{Binding Errors, Source={TemplateBinding Properties[PropertyName]}}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="Red" Text="{Binding}" Visibility="{Binding IsValid}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and Here is the result.
Something is strange. because I want to display Validation warning message for ONLY first name. but It display every warning. from address, from postal code.
My main quesiton
How to access "ValidatableModelBase.Property["PropertyName"].Errors" in Xaml.
because [] branket is not possible to use in XAML binding. How to accesss ??
with lot of my time, I finally find a solution for my own question...
First is my model class.
public class SettingEmail
: Template10.Validation.ValidatableModelBase
{public string EmailReceivers { get { return Read<string>(); } set { Write(value); } }}
Next is Property to bind. ( in my ViewModel class )
public SettingEmail SettingEmailModel{ get { return se; } set { this.Set(ref se, value); } }
Next is XAML code.
<validate:ControlWrapper DataContext="{Binding SettingEmailModel}"
PropertyName="EmailReceivers"
Style="{StaticResource validationNotify}">
<TextBox Text="{Binding EmailReceivers, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinHeight="400" Style="{StaticResource SettingStyle_MultilineTextBox}"/>
</validate:ControlWrapper>
and Last is Style in Resource file.
<Style x:Key="validationNotify" TargetType="validate:ControlWrapper">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="validate:ControlWrapper">
<StackPanel >
<ContentPresenter Content="{TemplateBinding Content}"/>
<ItemsControl DataContext="{TemplateBinding Property}"
ItemsSource="{Binding Errors, Source={TemplateBinding Property}}"
Style="{StaticResource validationNotifyMessage}"
>
<ItemsControl.ItemTemplate >
<DataTemplate>
<StackPanel>
<TextBlock Foreground="Red" Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I hope it help someone.
and I am sorry with my poor question's descriptions...I will try improve next...

Bind to child element of content template by name from outside

How can I get the binding from the outer ConnectingLine (a custom control that binds to FrameworkElements and connects them with a line) to the inner TextBlocks named "Top" and "Bottom" to work? Note that I want the whole FrameworkElements for position information.
<Grid>
<ConnectingLine From="{Binding ElementName=Button1.Top}" To="{Binding ElementName=Button2.Top}" />
<ConnectingLine From="{Binding ElementName=Button1.Bottom}" To="{Binding ElementName=Button2.Bottom}" />
<ToggleButton x:Name="Button1">
<ToggleButton.Template>
<ControlTemplate>
<Grid>
<Rectangle x:Name="Top" />
<Rectangle x:Name="Bottom" />
</Grid>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
<ToggleButton x:Name="Button2">
<ToggleButton.Template>
<ControlTemplate>
<Grid>
<Rectangle x:Name="Top" />
<Rectangle x:Name="Bottom" />
</Grid>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
My goal is to be able to bind from within XAML. Ideally with no extra fluff, but a solution involving a custom binding operator or attached properties might be acceptable.
Edit:
How I'd like to have the output:
Each distinct colored column is one of the templated ToggleButtons, already with one dashed ConnectingLine between Top and Bottom elements. The horizontal filled lines are what I'm interested in. Currently I'm achieving what I want from code-behind.
<ToggleButton x:Name="Button">
<ToggleButton.Template>
<ControlTemplate>
<Grid>
<CheckBox x:Name="FindMe" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked}"/>
</Grid>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
Let me know if it works.

Access a control from within a DataTemplate with its identifying name

In my WPF application I have a ComboBox control that is located inside a Grid Control. In XAML I am assigning a name to the ComboBox:
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Text="{Binding name_ru}" Width="Auto" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="stcom" Style="{DynamicResource ComboBoxStyle}" SelectionChanged="status_SelectionChanged" Height="auto" Width="Auto">
<ComboBox.BorderBrush>
<SolidColorBrush Color="{DynamicResource Color1}"/>
</ComboBox.BorderBrush>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
With the method FindName(string) I am trying to refer to the ComboBox with its associated name:
ComboBox stcom
{
get
{
return (ComboBox)FindName("stcom");
}
}
if (stcom != null)
{
stcom.ItemsSource = list;
}
But obviously the control can not be found because the reference stcom remains null.
The question now is how to refer to my ComboBox using its name property ?
The answer is:
<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Grid>
<TextBlock Name="tbUserIcon" Text="t1" />
<TextBlock Name="tbCheck" Text="✓" />
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and C#:
checkBox.ApplyTemplate();
var tbUserIcon= (TextBlock)checkBox.Template.FindName("tbUserIcon", checkBox);
don't forget the checkBox.ApplyTemplate() be fore Template.FindName() it's important!
First you have to get access to the control template which it has been applied to, then you can find an element of the template by name.
Have a look at the MSDN knowledge base :
How to: Find ControlTemplate-Generated Elements
You can't access controls that are part of a DataTemplate with their name.
You can try to read about some workarounds for example
WPF - Find a Control from DataTemplate in WPF
You can also have a look at the dozens of posts here on SO issuing this topic for example
here
here
here
here
here
here
here
here

WPF: Multiple content presenters in a custom control?

I'm trying to have a custom control that requires 2 or more areas of the XAML to be defined by a child control - that inherits from this control. I'm wondering if there's a way to define multiple contentpresenters and one which acts as the default content presenter
<MyControl>
<MyControl.MyContentPresenter2>
<Button Content="I am inside the second content presenter!"/>
</MyControl.MyContentPresenter2>
<Button Content="I am inside default content presenter" />
</MyControl>
Is this possible, how do I define this in the custom control's template?
The template can just bind the separate ContentPresenter instances like this (I've only set one property here but you'll likely want to set others):
<ContentPresenter Content="{TemplateBinding Content1}"/>
<ContentPresenter Content="{TemplateBinding Content2}"/>
The control itself should expose two properties for content and set the default using the ContentPropertyAttribute:
[ContentProperty("Content1")]
public class MyControl : Control
{
// dependency properties for Content1 and Content2
// you might also want Content1Template, Content2Template, Content1TemplateSelector, Content2TemplateSelector
}
You can use an "ItemsControl" with a custom template.
<ItemsControl>
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[0]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[1]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[2]}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
<TextBlock Text="Item 3"/>
</ItemsControl>
Here's another option that doesn't require making a custom control and is more typesafe than doing the ItemsControl thing (if type safety is something you want..perhaps not):
...Use an attached property!
Create an attached property of the appropriate type. We happened to need a text control so I did a string TextContent attached property. Then create a TemplateBinding to it from the template, and when instantiating in Xaml set it there as well. Works nicely.

Categories