Something Like For Loop in XAML - wpf

I have a Resource Dictionary in which I want to have a common DataTemplate for ComboBox.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate DataType="{x:Type ComboBox}">
<StackPanel Orientation="Horizontal">
<!--Here I need to use something like For Loop-->
<TextBlock Text=""></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
Now I have created a dependency property of type integer named NoOfColumns. While declaring the comboBox I need to set the NoOfColumns property to automatically generate that number of columns. I want them to databind.
Update as requested by Joe
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndCorrespondingEffects}"
IsEditable="True" SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}" TextSearch.TextPath="GroupName"
Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type vm:GroupAndCorrespondingEffect}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" Width="250">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding CorrespondingEffect}" />
</StackPanel>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>

There's nothing like for in XAML, but ItemsControl is very much like foreach. Instead of setting an int property, make an ObservableCollection<T> and add that many objects to it, and then bind the ItemsControl to your collection property.
This has the added benefit that each collection item can expose properties to be bound, e.g. if you wanted to display different text in each TextBlock, you could put a property on your collection item and bind the TextBlock to that property.

Related

Handling different item templates that share the same content

I have two custom ItemTemplates for the ListBox, one for the regular items, and one for the selected item. An example of how would I handle this is:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource Template2}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
Template1 and Template2 are very similar:
<DataTemplate x:Key="Template1">
<SameContent />
<DifferentContent1 />
</DataTemplate>
<DataTemplate x:Key="Template2">
<SameContent />
<DifferentContent2 />
</DataTemplate>
So, is it a proper way to duplicate the code for the SameContent (which is like a bunch of TextBlocks, Panels, etc) in both templates, or it is a better approach to have only one template, but switch the DifferentContent based on the IsSelected property, or...?
if second approach, how would it be properly done?
Obviously duplicating the code is not a very good solution. A better approach is to define another DataTemplate as your common content and then use ContentPresenter to present it:
<Window.Resources>
<DataTemplate x:Key="CommonTemplate">
<TextBlock Text="{Binding CommonProperty1}" />
<TextBlock Text="{Binding CommonProperty2}" />
</DataTemplate>
<DataTemplate x:Key="Template1" >
<StackPanel>
<ContentPresenter ContentTemplate="{StaticResource CommonTemplate}"/>
<TextBlock Text="{Binding Template1Property1}"/>
<TextBlock Text="{Binding Template1Property2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Template2" >
<StackPanel>
<ContentPresenter ContentTemplate="{StaticResource CommonTemplate}"/>
<TextBlock Text="{Binding Template2Property1}"/>
<TextBlock Text="{Binding Template2Property2}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>

ComboBoxItem Will Not Collapse

I wrote a basic visibility converter such that when the property "Active" is true the ComboBoxItem should be Visible, Collapsed otherwise. It currently displays the Active ones properly, the inactive ones Text are invisible but the item can still be seen.
http://snag.gy/Mh2Xq.jpg
May I ask how do I get the ComboBoxItem to collapse the comboboxitem that are inactive properly please.
<ComboBox Grid.Row="1" Grid.Column="2" SelectedItem="{Binding Product, Mode=TwoWay}" ItemsSource="{Binding Products}" VerticalContentAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="{Binding Active, Converter={StaticResource VisibilityConverter }}"></Setter>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Apply the visibility converter to the parent stackpanel instead. Like so:
<StackPanel Orientation="Horizontal" Visibility="{Binding Active, Converter={StaticResource VisibilityConverter}}">
...
</StackPanel>
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="Visibility" Value="{Binding Active, Converter={StaticResource VisibilityConverter}}" />
</Style>
</ComboBox.Resources>
You should filter the bound list on basis of IsActive
Try to Bind comboBox to
Products.Where(t => t.IsActive)

Need multiple styles dependent on Listboxitem

i have a Listbox, which stores two different object types, based on the same baseclass. (e.g. BaseObject = baseclass and the children of it: CustomPath and CustomImage)
The Datasource:
ObservableCollection<BattlegroundBaseObject> _baseObjectCollection;
public ObservableCollection<BattlegroundBaseObject> BaseObjectCollection
{
get { return _baseObjectCollection?? (_baseObjectCollection= new ObservableCollection<BaseObject>()); }
}
The Listbox databinding: <ListBox ItemsSource="{Binding BaseObjectCollection}"
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" x:Name="ListBoxPathLineStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem" x:Name="BattlegroundObjectControlTemplate">
<Path Stroke="{Binding ObjectColor}" StrokeThickness="{Binding StrokeThickness}" Data="{Binding PathGeometryData}" x:Name="PathLine" Opacity="{Binding Opacity}">
</Path>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect" TargetName="PathLine">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10" />
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
I want to add to the ControlTemplate where the Path is, also a Image and to differ it by type or a property. doesnt matter.
anyone any ideas?
You can add to ListBox resources DataTemplate for each type.
In my example classes Car and Motorbike derived from Vehicle class.
<ListBox x:Name="listBox">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<StackPanel Background="Red">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Motorbike}">
<StackPanel Background="Orange">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
EDIT:
You can add style for ListBoxItem to resources:
<ListBox x:Name="listBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type local:Car}">
<StackPanel Background="Red">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Motorbike}">
<StackPanel Background="Orange">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
You could define some DataTemplates for your different classes. They determine how the classes are displayed. I've been using them to display derived classes differently when working with a collection of the base class.
<DataTemplate DataType="{x:Type CustomPath}">
<TextBlock Text="This is a CustomPath"/>
</DataTemplate>
<DataTemplate DataType="{x:Type CustomImage}">
<TextBlock Text="This is a CustomImage"/>
</DataTemplate>
At the moment you are changing the style of the controls that WPF is using to render your bound data. A better way to do this is to provide WPF with a way of generating the correct controls. Ignore the ListBoxItem and use DataTemplates for your actual objects.
First you need to tell the Window or control how to find your types.
<Window or UserControl
...
xmlns:model="clr-namespace:yourNamespace"
>
Then you can provide WPF with a way to show your objects e.g.
<DataTemplate TargetType="{x:Type model:CustomPath}">
<Path Stroke="{Binding ObjectColor}" StrokeThickness="{Binding StrokeThickness}"
Data="{Binding PathGeometryData}" x:Name="PathLine" Opacity="{Binding Opacity}"/>
<!-- maybe use a binding from the Path.Effect back to the IsSelected and ValueConverters
to re-apply the selection effect-->
</DataTemplate>
<DataTemplate TargetType="{x:Type model:CustomImage}">
<Image Src="{Binding SomeProperty}" />
</DataTemplate>
Now all you need to do is to make these available to the ListBox in some way. Almost every element in WPF can have .Resources added to it, so you could choose to do these across the entire window
<Window ...>
<Window.Resources>
<DataTemplate .../>
<DataTemplate .../>
</Window.Resources>
...
<ListBox .../>
</Window>
or you can apply it more locally
<Window ...>
...
<ListBox>
<ListBox.Resources>
<DataTemplate .../>
<DataTemplate .../>
</ListBox.Resources>
</ListBox>
</Window>
And this way your listbox definition can become much neater too, e.g. if you are using Window.Resources
<ListBox ItemsSource="{Binding BaseObjectCollection}"/>

Change listbox template on lost focus event in WPF

I want to present a listbox with TextBlocks as items. When the user clicks/selects an item it changes into a TextBox for editing. As soon as the controls loses focus the item would turn back to a TextBlock.
The following XAML is almost working in that the TextBlock does turn into a TextBox when it's selected. It also turns back to a TextBlock if I select another item on the list. The problem is that if I move out of the listbox (in this case to the Add New text box) the list item stays as a TextBox.
The question ( WPF ListViewItem lost focus event - How to get at the event? ) looked promising but I can't make it work. I tried using the IsFocused property but then I wasn't able to edit the textbox because when I got into the textbox the listboxitem would go out of focus and thus turning back to a TextBlock before I had a chance to edit the text.
How can I make the TextBox turn back to TextBlock if the listbox/item loses the focus?
(Any other implementation that accomplishes the goal are also welcomed)
<Window x:Class="MyView.MainWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="MainWindow1" Height="300" Width="200">
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="SelectedTemplate">
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
<Setter Property="ContentTemplate" Value="{StaticResource ItemTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource SelectedTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel >
<ListBox ItemsSource="{Binding Departments}" HorizontalContentAlignment="Stretch"
ItemContainerStyle="{StaticResource ContainerStyle}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding NewDepartmentName, Mode=TwoWay}" />
<Button Grid.Column="1" Width="50" Content="Add" Command="{Binding Path=AddNewDepartmentCommand}" />
</Grid>
</StackPanel>
</Window>
This doesn't answer your question, but gives an alternative solution by making a textbox look like a textblock when the listboxitem isn't selected:
<Page.Resources>
<ResourceDictionary>
<Style x:Key="ListBoxSelectableTextBox" TargetType="{x:Type TextBox}">
<Setter Property="IsHitTestVisible" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}" Value="True">
<Setter Property="IsHitTestVisible" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Page.Resources>
<Grid>
<ListBox ItemsSource="{Binding Departments}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Margin="5" Style="{StaticResource ListBoxSelectableTextBox}" Text="{Binding Name}" BorderBrush="{x:Null}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
There are many ways you can do this:
Above answer
Create a TextBox style based on TextBoxBase such that when disabled, it makes the TextBox look like a TextBlock by setting the BorderThickness="0", Background="transparent"
Have both TextBlock and TextBox on each ListViewItem and using the above answer technique hide and show the TextBlock/TextBox accrodingly

wpf - ListBox - bind SelectedItem to an xml attribute?

I have a listbox on a usercontrol which is populated by a xml file.
<Machines xmlns="">
<Machine Name="Prod1" IP="192.168.1.200" isDefault="true" InstanceName="sql08" />
<Machine Name="Prod2" IP="192.168.1.101" />
<Machine Name="Test1" IP="192.168.1.103" />
<Machine Name="Test2" IP="192.168.1.104" />
</Machines>
I would like to bind the Listbox's Selected Item to the Machine which has a isDefault=true attribute.
My current xmldataprovider and ItemTemplate are listed below along with my ListBox markup. I was not sure if I needed to do some xpath binding in the datatemplate, or if I should make an explicit style with a trigger for this task? Or if either of those approaches would even work? One of the things I can't understand is how I can bind to an attribute that only exists on one node of my file.
<XmlDataProvider x:Key="DataList" Source="XML\ListboxSettings.xml" XPath="Machines/Machine"/>
<DataTemplate x:Key="MachineDataTemplate">
<TextBlock Text="{Binding XPath=#Name}" ToolTip="{Binding XPath=#IP}" />
</DataTemplate>
<ListBox Name="MerlinsListbox" Margin="5" Height="{Binding Height, ElementName=border}" Background="#FF252525" FontFamily="Consolas" FontSize="16" Foreground="#FFFBF9F9"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource MerlinDataTemplate}"
IsSynchronizedWithCurrentItem="true"/>
Two possible ways you could handle this are as follows:
1) You could set the ItemContainerStyle and bind the ListBoxItem's IsSelected property to the #isDefault attribute.
<ListBox Name="MerlinsListbox" Margin="5"
Background="#FF252525" FontFamily="Consolas" FontSize="16" Foreground="#FFFBF9F9"
ItemsSource="{Binding Source={StaticResource DataList}}"
ItemTemplate="{StaticResource MachineDataTemplate}"
IsSynchronizedWithCurrentItem="true">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding XPath=#isDefault, Mode=OneTime}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Or 2) add a trigger for the ItemContainerStyle:
<ListBox ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#isDefault}" Value="true">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

Resources