I have custom ContentControl
public class FilteringColumnHeader : ContentControl
{
public static readonly DependencyProperty TextFieldProperty =
DependencyProperty.Register("TextField", typeof(string), typeof(FilteringColumnHeader), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string TextField
{
get
{
return (string)GetValue(TextFieldProperty);
}
set { SetValue(TextFieldProperty, value); }
}
}
With this template style
<Style TargetType="{x:Type c:FilteringColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:FilteringColumnHeader}">
<DockPanel>
<ContentPresenter DockPanel.Dock="Top" Content="{TemplateBinding Content}" />
<TextBox Text="{TemplateBinding TextField}"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And this is how I use it in DataGrid in XAML
<DataGridTextColumn x:Name="NameColumn" Header="Name" Binding="{Binding Name}" Width="*" MinWidth="50">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<c:FilteringColumnHeader Content="{Binding }" Width="{Binding ActualWidth, ElementName=NameColumn}" TextField="{Binding DataContext.NameFilter, RelativeSource={RelativeSource AncestorType={x:Type local:GeneratorsListView}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
And GeneratorsListView
private string nameFilter = "rec";
public string NameFilter
{
get { return nameFilter; }
set
{
nameFilter = value;
}
}
My problem is, the Text binding works only OneWay. When I run the code TextBox will be filled with "rec" and when I change the NameFilter, the TextBox also changes. But when I type something in that box, nothing happens (setter of NameFilter is not being invoked at all). As you can see I've tried to set mode to TwoWay everywhere I could, still nothing. When I pleace regular TextBox inside DataTemplate and set the exact same Text binding, it is working.
{TemplateBinding} is an optimized version of a binding with a mode of OneWay so if you want the property to get updated you should use an ordinary binding with the RelativeSource set to TemplatedParent:
<ControlTemplate TargetType="{x:Type c:FilteringColumnHeader}">
<DockPanel>
<ContentPresenter DockPanel.Dock="Top" Content="{TemplateBinding Content}" />
<TextBox Text="{Binding TextField, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource TemplatedParent}}"/>
</DockPanel>
</ControlTemplate>
Related
The main idea is to have a button with default icon "yes.png" and text in it "Accept", but have a possibilty to change these two properties using only XAML(at the designing process, without compiling).
Current XAML window which has an area at the bottom with only two buttons:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<ResourceDictionary>
<Style x:Key="tb1" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderThickness="1" BorderBrush="#000" Padding="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source="Files/Icons/no.png" Margin="10,0,0,0" Height="16" Width="16"></Image>
<TextBlock Grid.Column="1" Margin="10" VerticalAlignment="Center">Cancel</TextBlock>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,10,10,10"></Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Border BorderThickness="0, 1, 0, 0" BorderBrush="#e7e7e7" HorizontalAlignment="Stretch" Padding="0,0,0,0" VerticalAlignment="Bottom" Height="61">
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="b_Accept" Style="{StaticResource tb1}"></Button> <!-- How to change an icon to "yes.png" and TextBlock's content to "Accept"? -->
<Button x:Name="b_Cancel" Style="{StaticResource tb1}"></Button>
</StackPanel>
</Border>
</Grid>
</Window>
Result:
Please,
How is it possible to change second button's icon to "no.png"(Source property) and TextBlock's text(Content) to "Cancel"(only using XAML and not User Control)?
What would be the very right way(the easiest?)? For example, in this post we might use DataTemplate, but might be that's not that we want to because DataTemplate changes the whole element, while we need only one property.
Although, I am right that there are only dependency property(C#) available for that purpose which expects compiling?
Thank you
You can create your custom Button class or an Attached Property to extend the Button:
public class IconControl : DependencyObject
{
#region IconUri attached property
public static readonly DependencyProperty IconUriProperty = DependencyProperty.RegisterAttached(
"IconUri", typeof(ImageSource), typeof(IconControl), new PropertyMetadata(default(ImageSource)));
public static void SetIconUri([NotNull] DependencyObject attachingElement, ImageSource value)
{
attachingElement.SetValue(IconControl.IconUriProperty, value);
}
public static ImageSource GetIconUri([NotNull] DependencyObject attachingElement) => (ImageSource) attachingElement.GetValue(IconControl.IconUriProperty);
#endregion
#region Label attached property
public static readonly DependencyProperty LabelProperty = DependencyProperty.RegisterAttached(
"Label", typeof(String), typeof(IconControl), new PropertyMetadata(default(String)));
public static void SetLabel([NotNull] DependencyObject attachingElement, String value)
{
attachingElement.SetValue(IconControl.LabelProperty, value);
}
public static String GetLabel([NotNull] DependencyObject attachingElement) => (String) attachingElement.GetValue(IconControl.LabelProperty);
#endregion
}
Modified Style for the Button:
<Style x:Key="IconButtonStyle"
TargetType="{x:Type Button}">
<!-- Set the default values -->
<Setter Property="IconControl.IconUri" Value="/Files/Icons/no.png"/>
<Setter Property="IconControl.Label" Value="Cancel"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderThickness="1"
BorderBrush="#000"
Padding="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(IconControl.IconUri)}"
Margin="10,0,0,0"
Height="16"
Width="16" />
<TextBlock Grid.Column="1"
Margin="10"
VerticalAlignment="Center"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(IconControl.Label)}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Margin"
Value="0,10,10,10"></Setter>
</Style>
Usage:
<!-- Override the default content -->
<Button Style="{StaticResource IconButtonStyle}"
IconControl.IconUri="/Files/Icons/yes.png"
IconControl.Label="Accept" />
I want to bind a DependencyProperty to my TextBox, what I need to do is to create a control that allows me to write a text in its property "Letter" and sets it as the text of the TextBlock defined in the template. I've never done this before so I'm not sure of how to do it.
Here's the .xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:My_App">
<Style TargetType="local:GameLetter" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:GameLetter">
<Grid>
<Image Source="Assets/imgs/letter_key.png"/>
<Viewbox Margin="10,0">
<TextBlock x:Name="textBlock" FontFamily="Assets/fonts/avenirnext.ttf#Avenir Next" Text="{Binding Letter}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And here's the .cs:
public sealed class GameLetter : Control
{
public GameLetter()
{
this.DefaultStyleKey = typeof(GameLetter);
}
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(GameLetter), new PropertyMetadata(null));
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set { SetValue(LetterProperty, value); }
}
}
You're close. The problem with your binding is that it'll search the Letter property on your datacontext, not on your control. You can fix that by using a TemplateBinding:
<Style TargetType="local:GameLetter" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:GameLetter">
<Grid>
<Image Source="Assets/imgs/letter_key.png"/>
<Viewbox Margin="10,0">
<TextBlock x:Name="textBlock" FontFamily="Assets/fonts/avenirnext.ttf#Avenir Next" Text="{TemplateBinding Letter}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is my first question here on SO...I have been a ready for a long time and never needed to ask for help because I usually find what I need, but I am having a hard time with this one...
I am working on a tools suite in WPF. I created a few User Controls as follow:
LabelTextBox (Label on the left and TextBox on the right)
LabelTextBoxToggle (LabelTextBox on the left and Checkbox on the right)
LabelTextBoxBrowseFile (LabelTextBox on the left and Browse File Button on the right)
I use Dependency Properties to bind all the properties I need and they all work fine. The problem I ran into recently is getting ValidationRules to work correctly on the base TextBox I use in LabelTextBox when those rules are applied to the LabelTextBoxToggle and LabelTextBoxBrowseFile UserControls, since I have to bind 2 levels down in order to update controls in LabelTextBox. I can get the Validation Rule to run, but I can't get the TextBox control to update its background color accordingly when errors are found, like I do when LabelTextBox isn't nested within another User Control.
So, here's my code below:
Style used for TextBox:
<!-- TextBox Default Style, Supports Validation Rules -->
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{StaticResource TextBoxBGDefault}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="{StaticResource TextBoxBGHasFocus}" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{StaticResource TextBoxBGHasFocus}" />
</Trigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError)}" Value="true">
<Setter Property="Background" Value="{StaticResource TextBoxBGHasError}" />
<Setter Property="BorderBrush" Value="Firebrick" />
<Setter Property="BorderThickness" Value="1.5" />
<Setter Property="ToolTipService.InitialShowDelay" Value="2" />
<Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors)[0].ErrorContent}" />
</DataTrigger>
</Style.Triggers>
</Style>
LabelTextBox.xaml:
<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=ControlRoot, Mode=OneWay, ValidatesOnDataErrors=True}">
<Grid.RowDefinitions>
<RowDefinition Height="24" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label
x:Name="NameLabel"
Width="{Binding Path=LabelWidth, Converter={StaticResource WidthToAutoConverter}}"
Margin="0"
HorizontalAlignment="{Binding Path=HorizontalContentAlignment}"
HorizontalContentAlignment="{Binding Path=LabelHAlign, Converter={StaticResource valueToStringConverter}}"
VerticalContentAlignment="Center"
Content="{Binding Path=LabelContent}"
Padding="10,2,5,2" />
<TextBox
x:Name="ValueTextBox"
Grid.Column="1"
KeyDown="TextBox_KeyDown_Enter"
Padding="5,0"
Text="{Binding TextBoxContent, Mode=TwoWay}"
TextChanged="TextBox_TextChanged" VerticalContentAlignment="Center" Height="22" VerticalAlignment="Center" />
<TextBlock
x:Name="ErrorMsgTextBlock"
Grid.Row="1"
Grid.Column="1"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Style="{DynamicResource ValidationErrorLabel}"
Text="{Binding Path=(Validation.Errors)[0].ErrorContent, ElementName=ControlRoot}"
Visibility="{Binding Path=(Validation.HasError), Converter={StaticResource BooleanToVisibilityConverter}, ElementName=ControlRoot, Mode=OneWay}" TextWrapping="Wrap" />
</Grid>
LabelTextBoxBaseClass:
#region TextBox Dependency Properties
public string TextBoxContent
{
get { return (string)GetValue( TextBoxContentProperty ); }
set { SetValue( TextBoxContentProperty, value ); }
}
public static readonly DependencyProperty TextBoxContentProperty =
DependencyProperty.Register( "TextBoxContent"
, typeof( string )
, typeof( LabelTextBoxBaseClass ), new PropertyMetadata( "" )
);
LabelTextBoxToggle.xaml:
<!-- This is the nested UserControl -->
<local:LabelTextBox
x:Name="LTBControl"
Margin="0"
VerticalContentAlignment="Center"
IsEnabled="{Binding Path=IsChecked, ElementName=ToggleCheckBox}"
LabelContent="{Binding Path=LabelContent}"
LabelHAlign="{Binding Path=LabelHAlign}"
LabelWidth="{Binding Path=LabelWidth}"
RaiseEnterKeyDownEvent="{Binding RaiseEnterKeyDownEvent, Mode=TwoWay}"
RaiseTextChangedEvent="{Binding RaiseTextChangedEvent, Mode=TwoWay}"
TextBoxContent="{Binding Path=TextBoxContent, Mode=TwoWay}" />
<CheckBox
x:Name="ToggleCheckBox"
Grid.Column="1"
Margin="5,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Click="ToggleCheckBox_Click"
IsChecked="{Binding CheckBoxChecked, Mode=TwoWay}" />
MaterialBuilder.xaml:
<UserControl.Resources>
<BindingGroup x:Key="SRBindingGroup" Name="PropertiesBindingGroup">
<BindingGroup.ValidationRules>
<local:AddMaterialRule ValidationStep="ConvertedProposedValue" />
</BindingGroup.ValidationRules>
</BindingGroup>
<srvalidators:StringNullOrEmptyValidationRule x:Key="stringNullOrEmptyValidationRule" ErrorMessage="Custom Dir cannot be null!" />
<srconverters:ListToStringConverter x:Key="ListToStringConverter" />
<srconverters:ListToStringConverter x:Key="listToStringConverter" />
<sys:String x:Key="newLine">\n</sys:String>
</UserControl.Resources>
<StackPanel x:Name="spSetup">
<!-- This contains a nested UserControl (LabelTextBox), and I can't get its TextBox background to change color, I just get the red border around the whole control on Validation Errors. -->
<srcontrols:LabelTextBoxBrowseFile
x:Name="ltbMaterialBlueprint"
Height="Auto"
Margin="0,5"
LabelContent="Material Blueprint:"
LabelWidth="120"
LostFocus="ltbMaterialBlueprint_UpdateUI"
OnButtonClick="ltbMaterialBlueprint_UpdateUI"
OnTextBoxEnterKeyDown="ltbMaterialBlueprint_UpdateUI"
TextBoxContent="{Binding MaterialBlueprintFilePath, Mode=TwoWay}">
<srcontrols:LabelTextBoxBrowseFile.TextBoxContent>
<Binding
Mode="TwoWay"
Path="CustomDirName"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<srvalidators:StringNullOrEmptyValidationRule ErrorMessage="Custom Dir cannot be empty!" />
</Binding.ValidationRules>
</Binding>
</srcontrols:LabelTextBoxBrowseFile.TextBoxContent>
</srcontrols:LabelTextBoxBrowseFile>
<!-- Here I use the base LabelTextBox control by itself and everything works as intended. The TextBox's background color changes to red on Validation Errors. -->
<srcontrols:LabelTextBox
x:Name="ltbMaterialName"
Margin="0,5,10,5"
LabelContent="Name:"
LabelWidth="60"
OnTextBoxTextChange="ltbMaterialName_Validate"
RaiseEnterKeyDownEvent="True"
RaiseTextChangedEvent="True">
<!-- Set-up the TextBox Content to use the ValidationRule by passing this GroupBox's BindingGroup resource as a parameter -->
<srcontrols:LabelTextBox.TextBoxContent>
<Binding
Mode="TwoWay"
Path="MaterialName"
UpdateSourceTrigger="Explicit"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:AddMaterialRule
BGroup="{StaticResource SRBindingGroup}"
CheckForDuplicates="True"
CheckForEmptyName="True"
IsMaterialName="True"
ValidationStep="ConvertedProposedValue" />
</Binding.ValidationRules>
</Binding>
</srcontrols:LabelTextBox.TextBoxContent>
</srcontrols:LabelTextBox>
</StackPanel>
I know it's probably a DataContext issue, but unlike the other controls and dependency properties, I cannot figure out how to make the base UserControl ui elements update their look when Validation Errors are found. Here's some images of what I mean:
Working TextBox (LabelTextBox control used here):
Working TextBox Example
Broken TextBox (LabelTextBoxToggle control used here, with nested LabelTextBox):
Broken TextBox (nested in UserControl)
Any help or suggestion is very welcomed of course! Thanks for your time!
Your problem is similar to mine. I've also created custom control containing text block (as label) and text box (as input). The goal is to have universal control for data input with simple label. The problem was validation. I've also managed easily to bind and validate data, but displaying errors with template on specified textbox that was inside my control... that was the issue and if I understand correctly you have the same problem. So my solution is:
<UserControl x:Class="CapMachina.Common.Controls.FormField_UC" x:Name="FormFieldCtrl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CapMachina.Common.Controls"
xmlns:Converters="clr-namespace:CapMachina.Common.Converters"
xmlns:metro="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Converters:ConditionalValueConverter x:Key="conditionalValueConverter" />
<Converters:NullableObjectToVisibilityConverter x:Key="nullableObjectToVisibilityConverter" />
</UserControl.Resources>
<StackPanel>
<TextBlock FontWeight="Bold" Text="{Binding Header, ElementName=FormFieldCtrl}" Margin="1" />
<TextBox x:Name="MainTxtBx" metro:TextBoxHelper.Watermark="{Binding WaterMarkText, ElementName=FormFieldCtrl}" TextWrapping="Wrap"
Text="{Binding Text, ElementName=FormFieldCtrl, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"
Margin="1" IsReadOnly="{Binding IsReadOnly, ElementName=FormFieldCtrl}" TextChanged="MainTxtBx_TextChanged" Loaded="MainTxtBx_Loaded">
<TextBox.Style>
<MultiBinding Converter="{StaticResource conditionalValueConverter}">
<Binding Path="IsReadOnly" ElementName="FormFieldCtrl" />
<Binding Path="ReadOnlyStyle" ElementName="FormFieldCtrl" />
<Binding Path="DefaultStyle" ElementName="FormFieldCtrl" />
</MultiBinding>
</TextBox.Style>
</TextBox>
</StackPanel>
</UserControl>
And code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace CapMachina.Common.Controls
{
public partial class FormField_UC : UserControl
{
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(FormField_UC));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(FormField_UC));
public string WaterMarkText
{
get { return (string)GetValue(WaterMarkTextProperty); }
set { SetValue(WaterMarkTextProperty, value); }
}
public static readonly DependencyProperty WaterMarkTextProperty =
DependencyProperty.Register("WaterMarkText", typeof(string), typeof(FormField_UC));
public bool IsReadOnly
{
get { return (bool)GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(FormField_UC), new PropertyMetadata(true));
public Style ReadOnlyStyle { get; set; }
public Style DefaultStyle { get; set; }
public FormField_UC()
{
ReadOnlyStyle = Application.Current.FindResource("ReadOnlyTextBox") as Style;
DefaultStyle = Application.Current.FindResource("DefaultTextBox") as Style;
InitializeComponent();
}
private void MainTxtBx_TextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrEmpty(MainTxtBx.Text) && IsReadOnly)
Visibility = Visibility.Collapsed;
else
Visibility = Visibility.Visible;
}
private void MainTxtBx_Loaded(object sender, RoutedEventArgs e)
{
BindingExpression mainTxtBxBinding = BindingOperations.GetBindingExpression(MainTxtBx, TextBox.TextProperty);
BindingExpression textBinding = BindingOperations.GetBindingExpression(this, TextProperty);
if (textBinding != null && mainTxtBxBinding != null && textBinding.ParentBinding != null && textBinding.ParentBinding.ValidationRules.Count > 0 && mainTxtBxBinding.ParentBinding.ValidationRules.Count < 1)
{
foreach (ValidationRule vRule in textBinding.ParentBinding.ValidationRules)
mainTxtBxBinding.ParentBinding.ValidationRules.Add(vRule);
}
}
}
}
Usage:
<Controls:FormField_UC Header="First name" IsReadOnly="False" HorizontalAlignment="Left" VerticalAlignment="Top">
<Controls:FormField_UC.Text>
<Binding Path="Person.FirstName" Mode="TwoWay">
<Binding.ValidationRules>
<VDRules:NamesValidationRule InventoryPattern="{StaticResource NamesRegex}">
<VDRules:NamesValidationRule.Attributes>
<Validation:ValidationAttributes IsRequired="True" />
</VDRules:NamesValidationRule.Attributes>
</VDRules:NamesValidationRule>
</Binding.ValidationRules>
</Binding>
</Controls:FormField_UC.Text>
</Controls:FormField_UC>
What i did was copy validation rules to nested text box after all bindings were created. You cannot modify binding after use, but you can add validation rules to it :)
It is very important to set certain properties inside custom control like:
<UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True>
cos you cannot set them afterwards. So setting them in usage line is not needed.
Normally I bind ObservableCollection to my own classes. But in this case I need to bind ObservableCollection of Strings in ListBox using MVVM into WPF.
My xml is
<Window x:Class="ListBoxDynamic.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:vm="clr-namespace:ListBoxDynamic.ViewModel">
<Grid Margin="0,0,-8,0">
<ListBox Width="100" Height="90" ItemsSource="{Binding ListOfItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<ToggleButton Command="{Binding SelectItemCommand}" CommandParameter="{Binding ElementName=Item}" >
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<TextBlock x:Name="Item" Text="{Binding What I must to write?}" />
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
My MainViewModel
private ObservableCollection<string> _listOfItems = new ObservableCollection<string>();
public ObservableCollection<string> ListOfItems
{
get { return _listOfItems; }
set { _listOfItems = value; RaisePropertyChanged("ListOfItems"); }
}
public ICommand SelectItemCommand { get; private set; }
int counter = 1;
public MainViewModel()
{
ListOfItems.Add("Add new Item");
SelectItemCommand = new RelayCommand<object>((x) => ExecuteSelectItemCommand(x));
}
But I don't know that I must to write in Binding TextBlock into ToggleButton.
Since data which back each ListBoxItem is string, hence DataContext of each ListBoxItem will be string.
Hence doing <TextBlock x:Name="Item" Text="{Binding }" /> will show you the string backing the listboxitem in the textblock
there are few issues I notices
the command is available in the view model instead of the data item, so use relative source to bind to command
"{Binding}" can be used to refer to current Item, so no need to use elementname syntax
then instead of placing a textbox inside toggle button you can make use of content presenter to make it more flexible
so this could go like this
<ListBox Width="100"
Height="90"
ItemsSource="{Binding ListOfItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<ToggleButton Command="{Binding SelectItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"
CommandParameter="{Binding}"
Content="{Binding}">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<ContentPresenter />
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My View is clickable like a radiobutton with about 7 other of these radiobuttons, but my listbox is not updating it's selected property unless I click outside of my radiobutton.
Basically I have a AbstractTask as my base. I have 7 child classes. I want to be able to select one of these AbstractTasks. That's all i'm after. So in my main window i have this.
<Window x:Class="AdvancedTaskAssigner.View.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:AdvancedTaskAssigner.View"
Title="MainWindowView" Height="300" Width="300" SizeToContent="Height">
<DockPanel>
<TextBlock Text="TextBlock" DockPanel.Dock="Top" />
<TextBlock Text="{Binding ElementName=listTasks, Path=SelectedItem.Name}" DockPanel.Dock="Top" />
<ListBox x:Name="listTasks" ItemsSource="{Binding Tasks}" HorizontalContentAlignment="Stretch" SelectedItem="{Binding IsSelected}">
<ListBox.ItemTemplate>
<DataTemplate>
<v:AbstractTaskView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Window>
since this is a library and not a application I had to put this in the Constructor of MainWindowView
MainWindowView.xaml.cs
public MainWindowView()
{
InitializeComponent();
var atvm = new ViewModel.MainWindowViewModel();
atvm.LoadTasks();
this.DataContext = atvm;
}
MainWindowViewModel.cs
class MainWindowViewModel
{
internal void LoadTasks()
{
var assembly = Assembly.GetAssembly(typeof(AbstractTask)).GetTypes().Where(t => t.IsSubclassOf(typeof(AbstractTask)));
Type[] typelist = GetTypesInNamespace(Assembly.GetAssembly(typeof(AbstractTask)), typeof(AbstractTask));
foreach (Type t in typelist)
{
if(!t.IsAbstract && t.BaseType.Equals(typeof(AbstractTask)))
{
tasks.Add(new AbstractTaskViewModel(t));
}
}
}
private Type[] GetTypesInNamespace(Assembly assembly, Type baseClass)
{
return assembly.GetTypes().Where(t => t.IsSubclassOf(baseClass)).ToArray();
}
private ObservableCollection<AbstractTaskViewModel> tasks = new ObservableCollection<AbstractTaskViewModel>();
public ObservableCollection<AbstractTaskViewModel> Tasks
{
get { return tasks; }
}
}
AbstractTaskViewModel.cs
public class AbstractTaskViewModel
{
public AbstractTaskViewModel(Type task)
{
if (!task.IsSubclassOf(typeof(AbstractTask)))
{
throw new NotSupportedException(string.Format("{0} is not a subclass of AbstractTask", task.Name));
}
Task = task;
}
public string Description
{
get
{
return GetCustomAttribute(0);
}
}
public string Name
{
get
{
return GetCustomAttribute(1);
}
}
public bool IsSelected{get;set;}
private string GetCustomAttribute(int index)
{
var descriptions = (DescriptionAttribute[])Task.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (descriptions.Length == 0)
{
return null;
}
return descriptions[index].Description;
}
protected readonly Type Task;
}
AbstractTaskView.xaml
<RadioButton
x:Class="AdvancedTaskAssigner.View.AbstractTaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" GroupName="AbstractTasks" Background="Transparent" IsChecked="{Binding IsSelected, Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Grid>
<Border x:Name="MyHead" BorderBrush="Black" BorderThickness="2" CornerRadius="5" Background="LightGray" Margin="20,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Panel.ZIndex="2">
<TextBlock Text="{Binding Name}" Margin="5,2" MinWidth="50" />
</Border>
<Border x:Name="Myoooo" BorderBrush="Black" BorderThickness="2" CornerRadius="5" Background="Transparent" Margin="0,10,0,0" Panel.ZIndex="1">
<TextBlock Text="{Binding Description}" Margin="5,15,5,2" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="MyHead" Property="Background" Value="LightGreen" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
this is what i am getting..
I want the green border to be the selected item. not what the Listbox's Selected item is. The bottom text box is the name of the selected item.
There are some issues related to binding IsChecked property of RadioButton. You can read about it here or here. You can apply the solution presented in the first link using your AbstractTaskView instead of RadioButton. Replace your listbox in MainWindowView with the code:
<ListBox x:Name="listTasks" ItemsSource="{Binding Tasks}" HorizontalContentAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<v:AbstractTaskView Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
You have to also remove the folowing part of code from AbstractTaskView:
IsChecked="{Binding IsSelected, Mode=TwoWay}"
I hope you don't need IsSelected property of AbstractTaskViewModel to be set. But if you do, you can set it for example in Checked event handler in AbstractTaskView code behind (I know it's not pretty solution).
I see you're binding the IsSelected (boolean) to the SelectedItem (object)