Using togglebutton for RowDetailsTemplate - wpf

Say I want to use RowDetailsTemplate in my WPF.
<Grid x:Name="LayoutRoot" Background="White">
<data:DataGrid x:Name="grdVwDetails" AutoGenerateColumns="False" RowDetailsVisibilityMode="Collapsed">
<data:DataGrid.RowDetailsTemplate>
<DataTemplate>
<data:DataGrid HeadersVisibility="None" ItemsSource="{Binding Path= Project}" AutoGenerateColumns="True">
</data:DataGrid>
</DataTemplate>
</data:DataGrid.RowDetailsTemplate>
<data:DataGrid.Columns>
<data:DataGridTemplateColumn>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="+" Click="HandleExpandCollapseForRow"></Button>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
<data:DataGridTextColumn Header="EmployeeID" Binding="{Binding EmpID}"></data:DataGridTextColumn>
<data:DataGridTextColumn Header="Name" Binding="{Binding Name}"></data:DataGridTextColumn>
<data:DataGridTextColumn Header="Address" Binding="{Binding Address}"></data:DataGridTextColumn>
</data:DataGrid.Columns>
</data:DataGrid>
</Grid>
In code behind I use event to change the button appearance.
private void HandleExpandCollapseForRow(object sender, RoutedEventArgs e)
{
Button expandCollapseButton = (Button)sender;
DataGridRow selectedRow = DataGridRow.GetRowContainingElement(expandCollapseButton);
if (null != expandCollapseButton && "+" == expandCollapseButton.Content.ToString())
{
selectedRow.DetailsVisibility = Visibility.Visible;
expandCollapseButton.Content = "-";
}
else
{
selectedRow.DetailsVisibility = Visibility.Collapsed;
expandCollapseButton.Content = "+";
}
}
It seems work. My question is that I don't want to use +/- for button. I want to use different image for expand/collapse.
And do it should be done in xaml instead of code behind.
The entire project can be found at http://www.a2zmenu.com/Blogs/Silverlight/Collapse-RowDetailstemplate-on-clicking-again.aspx
That is for Silverlight, I use it for WPF.

You could replace the Button in the DataTemplate with a ToggleButton with a custom template:
<ToggleButton Width="50" Height="50">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Image x:Name="img" Source="plus.png" />
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="img" Property="Source" Value="minus.png" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>

Related

How to set GridViewColumn Cell Template with Style Triggers

At the moment my grid is changing to Autogenerate columns so I cannot predefine the column style. I'm wondering how abouts do I set a style trigger for a column based on header value.
Working code with predefined columns with AutoGenerateColumns="True"
<telerik:GridViewColumn Header="Test">
<telerik:GridViewColumn.CellEditTemplate>
<DataTemplate x:Key="ToggleDataTemplate">
<telerik:RadToggleButton Content="+" Width="20" Height="20"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"/>
</DataTemplate>
</telerik:GridViewColumn.CellEditTemplate>
</telerik:GridViewColumn>
What I've tried with AutoGenerateColumns="False"
<telerik:RadGridView.Resources>
<Style TargetType="telerik:GridViewColumn">
<Style.Triggers>
<Trigger Property="Header" Value="Test">
<Setter Property="CellTemplate" Value="{StaticResource ToggleDataTemplate}" />
<Setter Property="CellEditTemplate" Value="{StaticResource ToggleDataTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</telerik:RadGridView.Resources>
<DataTemplate x:Key="ToggleDataTemplate">
<telerik:RadToggleButton Content="+" Width="20" Height="20"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"/>
</DataTemplate>
With what I tried above, the cell remains blank. How abouts do I get the button to appear dynamically?
You should be able to handle the AutoGeneratingColumn event and set the CellEditTemplate of all columns programmatically:
<telerik:RadGridView x:Name="grid" AutoGeneratingColumn="grid_AutoGeneratingColumn">
<telerik:RadGridView.Resources>
<DataTemplate x:Key="ToggleDataTemplate">
<telerik:RadToggleButton Content="+" Width="20" Height="20"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"/>
</DataTemplate>
</telerik:RadGridView.Resources>
</telerik:RadGridView>
private void grid_AutoGeneratingColumn(object sender, Telerik.Windows.Controls.GridViewAutoGeneratingColumnEventArgs e)
{
e.Column.CellEditTemplate = grid.Resources["ToggleDataTemplate"] as DataTemplate;
}

WPF Binding to a RadioButton GroupName

I have a dynamic ListView using a DataTemplate as follows:
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="#ccc">
<RadioButton GroupName="MyRadioButtonGroup">
<StackPanel Orientation="Horizontal" Margin="0,0,0,5" Width="{Binding ActualWidth, ElementName=CheckoutGrid}">
<TextBlock Text="{Binding Path=Test1, StringFormat='{}{0} - '}" />
<TextBlock Text="{Binding Path=Test2, StringFormat='{} test {0} - '}" />
<TextBlock Text="{Binding Path=Test, StringFormat='{} test {0}'}" />
</StackPanel>
</RadioButton>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
I would like to have a button disabled until one of the RadioButton IsChecked:
<RadioButton Padding="15,10" VerticalAlignment="Center">
<RadioButton.Style>
<Style TargetType="RadioButton" BasedOn="{StaticResource styleToggleButton4}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyRadioButtonGroup, Path=IsChecked}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
<TextBlock FontWeight="SemiBold" FontSize="14" Margin="0">NEXT</TextBlock>
</RadioButton>
So the problem is I do not know how to properly bind to the RadioButton GroupName="MyRadioButtonGroup". You will see in the DataTrigger above I am trying Binding="{Binding ElementName=MyRadioButtonGroup, Path=IsChecked}" Value="False", but that is not working for me since obviously it is a GroupName and not an x:Name.
Any suggestions on how to approach this properly? I would rather want to handle this on the front-end if at all possible.
You could bind your radio button to a command and then use the isChecked as a parameter. Then bind to a boolean value based off that. I think this may be more of a workaround though.
<RadioButton
Command="{Binding MyCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}"
GroupName="radio" />
<RadioButton
Command="{Binding MyCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}"
GroupName="radio" />
Then in code behind or VM, it would require implementing the ICommand Interface and INotifyPropertyChanged
public bool IsChecked
{
get { return isChecked;}
set
{
isChecked = value;
OnPropertyChanged();
}
}
private void OnMyCommand(object obj)
{
if(obj is bool)
{
IsChecked = (bool)obj;
}
}
A group has no IsChecked property that you can bind to.
If you don't have a source property that keeps track of whether there is at least one checked RadioButton and "want to handle this on the front-end" - which I guess means in the view - you could write some code that simply sets the Tag property of the ListView to true/false depending on whether there is a RadioButton selected. It is a one-liner:
private void ListView_Checked(object sender, RoutedEventArgs e) => lv.Tag = true;
<ListView x:Name="lv" ToggleButton.Checked="ListView_Checked">
<ListView.ItemTemplate>
...
<ListView.ItemTemplate>
</ListView>
<RadioButton Padding="15,10" VerticalAlignment="Center">
<RadioButton.Style>
<Style TargetType="RadioButton" BasedOn="{StaticResource styleToggleButton4}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=lv, Path=Tag}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
<TextBlock FontWeight="SemiBold" FontSize="14" Margin="0">NEXT</TextBlock>
</RadioButton>

WPF and DataGridComboBox

I'm getting in such a mess with using a Combobox in a WPF grid.
I'm trying to implement a simple Contact form which allows the user to select a Salutation from a Combobox.
class Contact
{
..
public int SalutationID
{
get { return _salutationid;}
set { _salutationid = value; }
}
}
class Salutation
{
public int ID
{
get { return _id;}
}
public string Description
{
get { return _description; }
}
}
..
and in code
ObservableCollection<Contact> Contacts = GetContacts();
ObservableCollection<Salutation> Salutations = GetSalutations();
grid.ItemsSource = Contacts;
colSalutations.ItemsSource = Salutations;
The relevant XAML is:
<DataGridComboBoxColumn x:Name="colSalutation" Header="Title" SelectedValueBinding="{Binding SalutationID}" SelectedValuePath="ID" DisplayMemberPath="Description" />
I only get an entry in the Salutation column for the last entry in the grid - but this row is invalid - it shouldn't be there (the entire row apart from this entry is blank). When I click to edit (on any row), a combo box appears with all the correct entries, but when I choose an item, it disappears and the field entry is blank.
I've looked at loads of examples and I appear to doing everything fine, but obviously not.
Can someone please show me where I'm going wrong? As you've probably realised, I'm new to WPF.
Thanks
I've also tried this:
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsDropDownOpen" Value="True" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Path=Description}"></TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsDropDownOpen" Value="True" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Path=Description}"></TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
But this, while displaying a combobox (and still nothing for any other rows) together with the correct data, will not allow me to move off the current row, and I haven't a clue why!
Hopefully, to clarify matters, below is the FULL XAML :
</Grid.Resources>
<DockPanel>
<DockPanel Name="ButtonPanel" DockPanel.Dock="Top" LastChildFill="false">
<Button DockPanel.Dock="Left" Content="Save" x:Name="btnSave" Click="btnSave_Click" Height="28"/>
<Button DockPanel.Dock="Right" Content="Cancel" x:Name="btnCancel" Click="btnCancel_Click" Height="28"/>
</DockPanel>
<ProgressBar DockPanel.Dock="Top" Name="pbLoading" Minimum="0" Maximum="1" Height="16" IsIndeterminate="True" Margin="0,0,0,16" />
<DataGrid DockPanel.Dock="Top" x:Name="dgContacts" AutoGenerateColumns="false" CellEditEnding="dgContacts_CellEditEnding" PreviewKeyDown="dgContacts_PreviewKeyDown" BeginningEdit="dgContacts_BeginningEdit" >
<DataGrid.Columns>
<mui:DataGridTextColumn x:Name="colFirstName" Header="First Name" Binding="{Binding fldForename}"/>
<mui:DataGridTextColumn x:Name="colLastName" Header="Last Name" Binding="{Binding fldSurname}" />
<mui:DataGridTextColumn x:Name="colEmailName" Header="Email" Binding="{Binding fldEmail}"/>
<mui:DataGridTextColumn x:Name="colPhoneNumber" Header="Telephone" Binding="{Binding fldPhoneNumber}" />
<mui:DataGridComboBoxColumn
x:Name="colSalutation" Header="Title"
SelectedItemBinding="{Binding SalutationID}" SelectedValuePath="ID"
DisplayMemberPath="Description">
</mui:DataGridComboBoxColumn>
<mui:DataGridTextColumn x:Name="colAddressLine1" Header="Address 1" Binding="{Binding colAddressLine1}" />
<mui:DataGridTextColumn x:Name="colAddressLine2" Header="Address 2" Binding="{Binding colAddressLine2}" />
<mui:DataGridTextColumn x:Name="colAddressLine3" Header="Address 3" Binding="{Binding colAddressLine3}" />
<mui:DataGridTextColumn x:Name="colCity" Header="City" Binding="{Binding fldCity}" />
<mui:DataGridTextColumn x:Name="colCounty" Header="County" Binding="{Binding fldCounty}" />
<mui:DataGridTextColumn x:Name="colPostCode" Header="Postcode" Binding="{Binding fldPostCode}" />
<mui:DataGridTextColumn x:Name="colCountry" Header="Country" Binding="{Binding fldCountry}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
</UserControl>
The setter in your ID propery is missing.

WPF ListView column selection

I have a ListView. When I select a row I want that only one cell was selected not the whole row. How can I get this?
Here is my styles and templates.
<ListView x:Name="List"
ItemsSource="{Binding }"
ItemContainerStyle="{DynamicResource ListItemStyle}">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn HeaderContainerStyle="{StaticResource myHeaderStyle}"
Header="1"
CellTemplate="{StaticResource myCellTemplate1}">
</GridViewColumn>
<GridViewColumn Header="2"
HeaderContainerStyle="{StaticResource myHeaderStyle}"
HeaderTemplate="{StaticResource myHeaderTemplate}"
CellTemplate="{StaticResource cellTemplate2}">
</GridViewColumn>
<GridViewColumn Header="3"
HeaderContainerStyle="{StaticResource myHeaderStyle}"
HeaderTemplate="{StaticResource myHeaderTemplate}"
CellTemplate="{StaticResource cellTemplate3}" />
<GridViewColumn Header="4"
HeaderContainerStyle="{StaticResource myHeaderStyle}"
HeaderTemplate="{StaticResource myHeaderTemplate}"
CellTemplate="{StaticResource cellTemplate4}"/>
</GridView>
</ListView.View>
</ListView>
<Style x:Key="ListItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}" >
<Grid SnapsToDevicePixels="True" Margin="0" Width="410" x:Name="GridSmall">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="0" />
<GridViewRowPresenter x:Name="Rows" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="myCellTemplate1">
<DataTemplate.Resources>
<local:NullIdConverter x:Key="NullIdConverterKey"/>
</DataTemplate.Resources>
<DockPanel x:Name="RR">
<TextBlock FontSize="18" x:Name="TxtBl"
HorizontalAlignment="Center"
Text="{Binding Path = Id}"/>
</DockPanel>
</DataTemplate>
Thanks.
If you're using .NET 3.5 SP1 or .NET 4, I'd suggest looking into the DataGrid rather than ListView.
I think it gives you much more flexibility over your data and has a property called SelectionUnit that you can set to "Cell" that gives you the functionality that you desire.
Unfortunately, I don't think there is an easy way to do the same with the ListView.
Well.....it isn't easy. Like Scott says you may have a lot easier life using SelectionUnit and SelectionMode in .Net 4 and its DataGrid control. But, if you want to do it like you started to then try this:
In XAML (I didn't do the whole thing as Template or a Style, just the one column to get it working) you need code such as this:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border Name="myOwnBorder" BorderBrush="Gray" BorderThickness="1,1,1,0" Margin="-6,0,-6,0">
<Grid Margin="6,0,6,0">
<TextBlock Text="{Binding}"/>
</Grid>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
...and then in the code behing (not good design practice but for the sake of demo) create a function such as:
static bool FindBorderInListView(DependencyObject dep, ListView listView,
out Border border, out ListViewItem lvItem)
{
border = null;
lvItem = null;
DependencyObject depObj = dep;
while (depObj != listView)
{
if (border == null && depObj is Border)
{
border = depObj as Border;
if (border.Name != "myOwnBorder")
{
border = null;
}
}
else if (depObj is ListViewItem)
{
lvItem = depObj as ListViewItem;
}
depObj = VisualTreeHelper.GetParent(depObj);
}
return border != null && lvItem != null;
}
and then call it from the PreviewMouseDown event of your ListView:
private void MyList_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Border border;
ListViewItem lvItem;
if (FindBorderInListView(e.OriginalSource as DependencyObject, this.MyList,
out border, out lvItem))
{
ItemContainerGenerator generator = this.MyList.ItemContainerGenerator;
int rowIndex = generator.IndexFromContainer(lvItem);
int columnIndex = Grid.GetColumn(border);
MessageBox.Show("Cell #:" + rowIndex + columnIndex);
}
}
Credit where credit is due, Josh Smith worked out how to do this back in about 2007 I think in a reply to an MSDN community question.
Good luck with it! I had to implement something similar in a DataGrid once too.

Dynamically change the content of of control based on a value

I want to achieve the following behavior:
Depending on a value use a different datatemplate:
<DataTemplate x:Key="cardTemplate2">
<Border x:Name="container">
.....
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ShowSecondDT}" Value="True">
<Setter Property="Child" TargetName="container">
<Setter.Value>
<StackPanel Orientation="Vertical" >
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
The application fails claiming that Setter Property="Child" is null...
Another information is that this Datatemplate in the resource of a control: (devexpress gris)
<dxg:GridControl xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
x:Name="gridTitulaire" DataSource="{Binding Contacts}" Width="600" >
<dxg:GridControl.Resources>
<DataTemplate x:Key="cardTemplate2">
<Border x:Name="container">
<StackPanel Orientation="Horizontal" >
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding isTitulairePrincipal}" Value="True">
<Setter Property="Child" TargetName="container">
<Setter.Value>
<StackPanel Orientation="Vertical" >
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</dxg:GridControl.Resources>
<dxg:GridControl.Columns>
<dxg:GridColumn FieldName="first_name"/>
<dxg:GridColumn FieldName="last_name"/>
</dxg:GridControl.Columns>
<dxg:GridControl.View>
<dxg:CardView x:Name="view" ShowGroupedColumns="True" CardTemplate="{DynamicResource cardTemplate2}" />
</dxg:GridControl.View>
</dxg:GridControl>
Any idea ?
Thanks
Jonathan
Define two separate data templates (call them CardTemplateV for the vertical one, and CardTemplateH for the horizontal one, for example), and set the CardTemplateSelector to a selector object that checks the discriminating field.
Example:
class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var cardData = item as CardData;
if (cardData == null) return null;
var dataObject = cardData.DataContext as YourDataType;
if (dataObject == null) return null;
if (dataObject.isTitulairePrincipal)
return (DataTemplate)App.Current.FindResource("CardTemplateV");
else
return (DataTemplate)App.Current.FindResource("CardTemplateH");
}
}
In the XAML add the selector to the resource dictionary and reference it from the grid:
<Window.Resources>
<local:YourTemplateSelector x:Key="MyTemplateSelector"/>
</Window.Resources>
...
<dxg:CardView
x:Name="view"
ShowGroupedColumns="True"
CardTemplateSelector="{StaticResource MyTemplateSelector}"/>
...

Resources