The latest in my endeavors - I've managed to populate a treeview with elements, and populate a listbox next to it with related elements.
The trick is that, I'm trying to think of a way to assign each listbox item and corresponding treeview item it's own grid row, that would be generated probably in back C# code to with corresponding row definitions based on the control's number of children, if that makes any sense.
I've tried looking at the post below to see if I can select each treeview item this way, but to no avail:
How to programmatically select an item in a WPF TreeView?
Here's some example code that tries to implement this - I keep getting null values, though, and using UpdateLayout() before the loop on the controls doesn't seem to help. :/
for (int i = 0; i < loadedAR.Item2.Count; i++)
{
DABsAndIssuesGrid.RowDefinitions.Add(new RowDefinition());
var selectedItemObjectDAB = DABView.Items.GetItemAt(i);
var DABParent = DABView.Parent;
TreeViewItem currentItemDAB = DABView.ItemContainerGenerator.ContainerFromItem(selectedItemObjectDAB)
as TreeViewItem;
if (currentItemDAB != null)
Grid.SetRow(currentItemDAB, i);
var selectedItemObjectErr = IssueBox.Items.GetItemAt(i);
TreeViewItem currentItemErr = DABView.ItemContainerGenerator.ContainerFromItem(selectedItemObjectErr)
as TreeViewItem;
if (currentItemErr != null)
Grid.SetRow(currentItemErr, i);
}
Is there a better way to do this than the way I'm trying to do it? I've tried using a converter to assign the row dynamically too as they would get generated, but that still appears to put all of my children in the same first grid row...
Here's my XAML:
<Window x:Class="Client_Invoice_Auditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Client_Invoice_Auditor"
xmlns:self="clr-namespace:Client_Invoice_Auditor.CoreClient"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="1000">
<Window.Resources>
<self:SelfPayConverter x:Key="FINConverter"/>
<self:ErrorExpandConverter x:Key="ErrorExpandConverter"/>
<self:AssignRowConverter x:Key="AssignRowConverter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="80*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<StackPanel Orientation="Vertical">
<DockPanel VerticalAlignment="Top" Height="20" Panel.ZIndex="1">
<Menu Name="fileMenu" Width="Auto" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Open Account File" Click="menuOpenFile_Click"/>
<MenuItem Header="Exit" Click="menuExit_Click"/>
</MenuItem>
<MenuItem Header="Options">
<!--<MenuItem Header="Update" Click="update_Click"/>-->
<MenuItem Header="About" Click="about_Click"/>
</MenuItem>
</Menu>
</DockPanel>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Center" Height="Auto">
<StackPanel Width="Auto" Orientation="Horizontal" HorizontalAlignment="Center">
<Border BorderBrush="MediumAquamarine" BorderThickness="2">
<Label Name="AccountNumber"/>
</Border>
<Border BorderBrush="MediumAquamarine" BorderThickness="2">
<Label Name="AcctDesc"/>
</Border>
<Border BorderBrush="MediumAquamarine" BorderThickness="2">
<Label Name="Organization"/>
</Border>
</StackPanel>
</WrapPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Label Margin="20,10,0,0" Content="Activity Date Time" />
<Label Margin="60,10,0,0" Content="Beginning Balance" />
<Label Margin="10,10,0,0" Content="Charge Amount" />
<Label Margin="30,10,0,0" Content="Adjustments" />
<Label Margin="40,10,0,0" Content="Payments" />
<Label Margin="60,10,0,0" Content="End Balance" />
<Label Margin="50,10,0,0" Content="Issues" />
</StackPanel>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Grid.Column="0">
<Grid Name="DABsAndIssuesGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*"/>
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<TreeView Name="DABView" Grid.Column="0">
<!--<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding FinClass, Converter={StaticResource ErrorExpandConverter}}" />
</Style>-->
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding Dummies, Converter={StaticResource ErrorExpandConverter}}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type self:dailyAccountBalance}" ItemsSource="{Binding Dummies}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" IsEnabled="False">
<TextBlock Width="150" Text="{Binding DabActivityDate}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding BegBalance}"/>
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding ChrgAmount}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding AdjAmount}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding PmtAmount}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding EndBalance}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type self:DummyItem}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<!--<TextBlock Text="{Binding Text}" />-->
<DataGrid ItemsSource="{Binding ChargeActivities}" AutoGenerateColumns="False">
<DataGrid.Style>
<Style TargetType="{x:Type DataGrid}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count,
RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn x:Name="ChrgID" Header=" Charge" Binding="{Binding ChargeID}" />
<DataGridTextColumn x:Name="ChrgType" Header="Charge Type" Binding="{Binding ChargeType}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="CR">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn x:Name="ChrgAmt" Header="Amount" Binding="{Binding ChargeAmount}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ChargeType}" Value="CR">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn x:Name="FIN" Header="FIN" Binding="{Binding EncntrAlias}" />
<DataGridTextColumn x:Name="FINClass" Header="FIN Class" Binding="{Binding FinClass}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{Binding FinClass, Converter={StaticResource FINConverter}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
<!--<TreeViewItem x:Key="Test">
<TextBlock Text="Me gusta"></TextBlock>
</TreeViewItem>-->
</TreeView.ItemTemplate>
</TreeView>
<Border Grid.Column="1" BorderBrush="Black" BorderThickness="2">
<ListBox Name="IssueBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="Hi"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</Grid>
</Grid>
</Window>
And, here's the code behind:
using Client_Invoice_Auditor.CoreClientAR;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Client_Invoice_Auditor
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string accountFile;
public MainWindow()
{
InitializeComponent();
OpenaccountFile();
}
private void OpenaccountFile()
{
accountFile = "";
//errorFilters.Clear();
// Displays an OpenFileDialog so the user can select a file.
Microsoft.Win32.OpenFileDialog openFileDialog1 = new Microsoft.Win32.OpenFileDialog();
openFileDialog1.Filter = "Account Files|*.acct";
openFileDialog1.Title = "Select an account File";
if (openFileDialog1.ShowDialog() == true)
{
// Assign the cursor in the Stream to the Form's Cursor property.
accountFile = openFileDialog1.FileName;
//claimFile = openFileDialog1.OpenFile().ToString();
//openFileDialog1.Dispose();
}
if (accountFile == "")
{
/*System.Windows.MessageBox.Show("File must be selected in order to continue - exiting now."
, "No File Selected", MessageBoxButton.OK);
this.Close();*/
if (!AcctDesc.HasContent)
{
AcctDesc.Content = "No Account File Loaded";
//Version version = Assembly.GetExecutingAssembly().GetName().Version;
//manualBreakItem.IsEnabled = false;
//manualValidateItem.IsEnabled = false;
}
}
else
{
//openFileDialog1 = null;
Console.WriteLine("Account file path is: " + accountFile);
//claimFile = "C:\\Users\\KO054202\\Documents\\pft_1450_out\\pft_1450_out\\CAR837I125114220170601.out";
DataTable dataAR = new DataTable();
try
{
Tuple<accountARHeader, List<dailyAccountBalance>, DataTable> loadedAR = dabARLoader.LoadARData(accountFile);
//dataAR = loadedAR.Item2;
AccountNumber.Content = "Account Number: " + loadedAR.Item1.AccountNumber;
AcctDesc.Content = "Description: " + loadedAR.Item1.AccountDescription;
Organization.Content = "Client Organization: " + loadedAR.Item1.OrganizationName;
//TreeViewItem dummy = new TreeViewItem();
//dummy.DataContext = "Hi";
//DummyItem testThis = new DummyItem("Test2");
//testThis.Text = "Hi";
//loadedAR.Item2.First().Dummies.Add(testThis);
DABView.ItemsSource = loadedAR.Item2;
IssueBox.ItemsSource = dabARLoader.loadIssues(loadedAR.Item2);
DABView.UpdateLayout();
for (int i = 0; i < loadedAR.Item2.Count; i++)
{
DABsAndIssuesGrid.RowDefinitions.Add(new RowDefinition());
var selectedItemObjectDAB = DABView.Items.GetItemAt(i);
var DABParent = DABView.Parent;
TreeViewItem currentItemDAB = DABView.ItemContainerGenerator.ContainerFromItem(selectedItemObjectDAB)
as TreeViewItem;
if (currentItemDAB != null)
Grid.SetRow(currentItemDAB, i);
var selectedItemObjectErr = IssueBox.Items.GetItemAt(i);
TreeViewItem currentItemErr = DABView.ItemContainerGenerator.ContainerFromItem(selectedItemObjectErr)
as TreeViewItem;
if (currentItemErr != null)
Grid.SetRow(currentItemErr, i);
}
//DABView.DisplayMemberPath = "A";
}
catch (Exception e)
{
System.Windows.MessageBox.Show("I don't wanna open this file! Try another. Error: " + e.Message);
OpenaccountFile();
}
}
}
private void menuOpenFile_Click(object sender, RoutedEventArgs e)
{
OpenaccountFile();
}
private void menuExit_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void about_Click(object sender, RoutedEventArgs e)
{
System.Windows.MessageBox.Show("I heard you like clicking buttons.");
}
}
}
I have a TreeView inside of a Grid, along with a ListBox control. The
idea is that the TreeView would populate parent items by a daily
account balance, and then for each one of those an adjacent list of
issues would be displayed in the neighboring ListBox
I would make specialized Data Transfer Object (DTO)s where each instance item generates the values for the properties to be shown and where required creates child DTOs for that instance. Then those DTOs are bindable between all the controls. Meaning that you have this class defined
<Top Level Class used by the grid<DTO>>
<Properties et all>
<List of Items in for treeview>
<Treeview class <DTO>>
<Properties et all>
<List of Items in for listbox>
<Listbox class <DTO>>
<Properties et all>
Then bind the grid to the above top level DTO list.
Bind the treeview to the current row's treeview list items.
Bind the listbox to the current treeview's selected item's Listbox items list.
Once the DTOs are created then it simply becomes an exercise in binding.
Related
I grouped my items (products of order) from database by using DataGrid.Group, I grouped it as orders, so it looks like this: Order Number: #{Here I placed real ID from database }, example Order Number: #10, and what I want to do now, is to place button next to that text which is contained in dock panel, and when I click on that button I want to do some action with that orders and because I allready have real order id in header I should "take" it somehow, so I can do in code behind something like : remove order, mark it as proceed or something like that, here is how it looks right now and below is code also:
<DataGrid.Columns >
<DataGridTextColumn Binding="{Binding ProductName}" ElementStyle="{StaticResource LeftAligElementStyle}" Header="Product Name}" MinWidth="350" Foreground="White" FontSize="20" FontFamily="Verdana" />
<DataGridTextColumn Binding="{Binding Quantity}" ElementStyle="{StaticResource LeftAligElementStyle}" Header="Quantity" MinWidth="200" Foreground="White" FontSize="20" FontFamily="Verdana" />
</DataGrid.Columns>
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="Black" Opacity="0.7">
<Expander.Header >
<DockPanel Height="50" Margin="0,0,0,0" Name="dockPanel" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}, Path=ActualWidth}">
<Button Name="btnOrders" Content="Test" Margin="0,0,200,0" DockPanel.Dock="Right" />
<TextBlock FontWeight="Normal" FontFamily="Verdana" FontSize="20" Height="25" Foreground="#83D744" Text="{Binding Path=Name,StringFormat= Order Number:# {0}}" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
Code behind:
public MainWindow()
{
try
{
InitializeComponent();
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.WindowState = WindowState.Maximized;
CollectionViewSource collectionViewSource = new CollectionViewSource();
var ordersList = OrdesrController.localOrders();
collectionViewSource.Source = ordersList;
collectionViewSource.GroupDescriptions.Add(new PropertyGroupDescription("NumberOfOrder"));
DataContext = collectionViewSource;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Handle Click event of your Button.
<Button Name="btnOrders" Click="Button_Click" .../>
private void Button_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
CollectionViewGroup group = b.DataContext as CollectionViewGroup;
/* make clever use of this group */
}
I have generated an expandable Listview in Windows Phone 8.1. I'm switching templates on item selection changed and item click.
<ListView Name="FiltersListview" ItemContainerStyle="{StaticResource StretchItemStyle}" ItemTemplate="{StaticResource CollapsedTemplate}" SelectionChanged="FiltersListview_SelectionChanged" IsItemClickEnabled="True" ItemClick="FiltersListview_ItemClick" Grid.Row="1" Grid.ColumnSpan="2"/>
<DataTemplate x:Name="CollapsedTemplate">
<Grid Height="50">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Style="{StaticResource PageTextStyle}"/>
<Image Margin="10" Grid.Column="1" Source="/Images/arrow-down.png"/>
<Rectangle Fill="Black" VerticalAlignment="Bottom" Height="1" Grid.ColumnSpan="2"/>
</Grid>
</DataTemplate>
<DataTemplate x:Name="ExpandedTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Style="{StaticResource PageTextStyle}"/>
<Image Margin="10" Grid.Column="1" Source="/Images/arrow-up.png"/>
<ListView Margin="20,0" Grid.Row="1" RequestedTheme="Light" Grid.ColumnSpan="2" ItemsSource="{Binding SubList}" SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="FontSize" Value="22"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</DataTemplate>
and then template changing
DataTemplate dtSmall, dtLarge;
private void FiltersListview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
if (e.RemovedItems.Count > 0)
{
foreach (var item in e.RemovedItems)
{
((ListViewItem)(sender as ListView).ContainerFromItem(item)).ContentTemplate = dtSmall;
}
}
if (e.AddedItems.Count > 0)
{
foreach (var item in e.AddedItems)
{
((ListViewItem)(sender as ListView).ContainerFromItem(item)).ContentTemplate = dtLarge;
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
private void FiltersListview_ItemClick(object sender, ItemClickEventArgs e)
{
try
{
if ((sender as ListView).SelectedItem != null)
{
if ((sender as ListView).SelectedItem.Equals(e.ClickedItem))
(sender as ListView).SelectedItem = null;
else
(sender as ListView).SelectedItem = e.ClickedItem;
}
else
(sender as ListView).SelectedItem = e.ClickedItem;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
Now when expandable list is opened I want to retain the selected item status in sublist but as the template is changed and a new listview is created it is not retaining the selected items. Can somebody help?
Wow. You are sort of cracking nuts with a steamroller here. Instead of switching templates, why not create a sophisticated Data Template that when selected shows secondary content?
If you derive from ListView, you can override PrepareContainer:
public class MyListView : Windows.UI.Xaml.Controls.ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var item = element as Windows.UI.Xaml.Controls.ListViewItem;
base.PrepareContainerForItemOverride(element, item);
}
}
In here, you have access to the IsSelected property which you can bind to your model (just create a property) and surface in your data template. Using this value, you can switch between content in your data template.
I would use behaviors so I wouldn't have to think very hard about this. Something like this:
<Grid>
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding IsSelected}" Value="True">
<Core:ChangePropertyAction TargetObject="{Binding ElementName=view1}" PropertyName="Visibility" Value="Visible"/>
<Core:ChangePropertyAction TargetObject="{Binding ElementName=view2}" PropertyName="Visibility" Value="Collapsed"/>
</Core:DataTriggerBehavior>
<Core:DataTriggerBehavior Binding="{Binding IsSelected}" Value="False">
<Core:ChangePropertyAction TargetObject="{Binding ElementName=view1}" PropertyName="Visibility" Value="Visible"/>
<Core:ChangePropertyAction TargetObject="{Binding ElementName=view2}" PropertyName="Visibility" Value="Collapsed"/>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Grid>
Since your context never really changes, neither will your List index.
Best of luck!
I am trying to show different Groups in a Listview which contain an expander and a header. However I don't want my SecondLevel Group to show a header if the Group only contains 1 item.
Since this would be quite inconvenient.
My Code:
<Window x:Class="ListViewGrouping.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:listViewGrouping="clr-namespace:ListViewGrouping"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<listViewGrouping:GroupItemStyleSelector x:Key="groupItemStyleSelector"/>
<!-- Style for the first level GroupItem -->
<Style x:Key="FirstLevel" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="3.5,0" Text="{Binding Name}" TextBlock.FontWeight="Bold"/>
<TextBlock Grid.Column="1" Margin="3.5,0" Text="Elements:"/>
<TextBlock Grid.Column="2" Margin="3.5,0" Text="{Binding ItemCount}"/>
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Style for the second level GroupItem -->
<Style x:Key="SecondLevel" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Margin="15,0,0,0">
<Expander.Header>
<TextBlock Text="{Binding Name}" TextBlock.FontWeight="Bold"/>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ListView Name="mailView" ItemsSource="{Binding}">
<ListView.GroupStyle>
<GroupStyle ContainerStyleSelector="{StaticResource groupItemStyleSelector}" />
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}"/>
<GridViewColumn Header="Subject" DisplayMemberBinding="{Binding Subject}"/>
<GridViewColumn Header="Sender" DisplayMemberBinding="{Binding Sender}"/>
<GridViewColumn Header="Support-ID" DisplayMemberBinding="{Binding Support_ID}"/>
<GridViewColumn Header="Supporter" DisplayMemberBinding="{Binding Supporter}"/>
<GridViewColumn Header="Received" DisplayMemberBinding="{Binding ReceivedDate}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
Code behind:
private void createMailList()
{
_mails.Add(new Mail("LIC", "Lizenz geht nicht", "Xeun", "LIC.2013.01.10.002", "Xeun", "25.09.2013"));
_mails.Add(new Mail("CD", "Alles doof", "Xeun", "CD.2013.01.10.002", "Xeun", "25.09.2013"));
_mails.Add(new Mail("CD", "Re:Alles doof", "Xeun", "CD.2013.01.10.002", "Xeun", "25.09.2013"));
_mails.Add(new Mail("CD", "CD kaputt", "Xeun", "CD.2013.01.10.003", "Xeun", "25.09.2013"));
_mails.Add(new Mail("CD", "Geht nicht", "Xeun", "CD.2013.01.10.001", "Xeun", "25.09.2013"));
_mails.Add(new Mail("LIC", "Kaputt", "Xeun", "LIC.2013.01.10.001", "Xeun", "25.09.2013"));
}
public MainWindow()
{
InitializeComponent();
createMailList();
DataContext = _mails;
ICollectionView view = CollectionViewSource.GetDefaultView(_mails);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("ID");
view.GroupDescriptions.Add(groupDescription);
view.GroupDescriptions.Add(new PropertyGroupDescription("Support_ID"));
}
}
public class GroupItemStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
Style s;
CollectionViewGroup group = item as CollectionViewGroup;
Window window = Application.Current.MainWindow;
if (!group.IsBottomLevel)
{
s = window.FindResource("FirstLevel") as Style;
}
else
{
s = window.FindResource("SecondLevel") as Style;
}
return s;
}
}
I hope I explained my problem well enough - I have attached a screenshot of the small app - the Groups marked as red only contain one item and should not be shown as group.
For both styles split the ControlTemplate in 2, one with an expander and one with out.
Create a converter which checks the if the group size (your group is of type CollectionViewGroup)
return yourGroup.Items.Count > 1
Place a DataTrigger as seen below in each style which checks the groups size via the converter
(your DataContext is your group so the binding is Binding="{Binding}"
xaml :
<ControlTemplate TargetType="{x:Type GroupItem}" x:Key="withExpander">
<Expander IsExpanded="True">
<Expander.Header>
.....
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
<ControlTemplate TargetType="{x:Type GroupItem}" x:Key="withOutExpander">
<ItemsPresenter />
</ControlTemplate>
<Style x:Key="FirstLevel" TargetType="{x:Type GroupItem}">
<Setter Property="Template" Value="{StaticResource withExpander}" />
<Style.Triggers>
<DataTrigger Binding="{Binding , Converter={StaticResource GroupSizeToExpanderConverter}" Value="False">
<Setter Property="Template" Value="{StaticResource withOutExpander}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Edit :
*the converter value will be the Group itself (of type CollectionViewGroup)
The Converter :
public class GroupSizeToExpanderConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
CollectionViewGroup grp = (CollectionViewGroup)value;
return grp.Items.Count() > 1; // ALTERNATIVLY grp.ItemCount;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
here is my solution
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Expander IsExpanded="True">
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ItemCount}" Value="1">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
<TextBlock Text=" item(s)" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<Border BorderBrush="LightBlue" BorderThickness="2" Margin="0 2">
<ItemsPresenter />
</Border>
</Expander>
<ItemsPresenter>
<ItemsPresenter.Style>
<Style TargetType="ItemsPresenter">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ItemCount}" Value="1">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsPresenter.Style>
</ItemsPresenter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
I write a program for edition of IIS web.config file. I have a problem with my TreeView control which doesn't want to refresh itself after I change something in the source XmlDocument variable.
It's the WPF project.
Window resources in XAML:
<Window.Resources>
<XmlDataProvider x:Key="XmlData" />
</Window.Resources>
My TreeView:
<TreeView x:Name="XmlTree" Grid.Row="1"
ItemsSource="{Binding Source={StaticResource XmlData}, XPath=*}"
ItemTemplate="{StaticResource NodeTemplate}"
SelectedItemChanged="XmlTree_SelectedItemChanged" />
TreeView style:
<DataTemplate x:Key="AttributeTemplate">
<StackPanel Orientation="Horizontal"
Margin="3,0,0,0"
HorizontalAlignment="Center">
<TextBlock Text="{Binding Path=Name}"
Foreground="{StaticResource xmAttributeBrush}" FontFamily="Consolas" FontSize="8pt" />
<TextBlock Text="=""
Foreground="{StaticResource xmlMarkBrush}" FontFamily="Consolas" FontSize="8pt" />
<TextBlock Text="{Binding Path=Value}"
Foreground="{StaticResource xmlValueBrush}" FontFamily="Consolas" FontSize="8pt" />
<TextBlock Text="""
Foreground="{StaticResource xmlMarkBrush}" FontFamily="Consolas" FontSize="8pt" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="NodeTemplate">
<StackPanel Orientation="Horizontal" Focusable="False">
<TextBlock x:Name="tbName" Text="?" FontFamily="Consolas" FontSize="8pt" />
<ItemsControl
ItemTemplate="{StaticResource AttributeTemplate}"
ItemsSource="{Binding Path=Attributes}"
HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
<HierarchicalDataTemplate.ItemsSource>
<Binding XPath="*" />
</HierarchicalDataTemplate.ItemsSource>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Value}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
<Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
Code behind:
private XmlDocument _xml;
private XmlElement _selectedElement; // actually selected item in TreeView
private XmlDataProvider _xmlDataProvider;
private string _tempFileName = #"C:\test.xml";
public MainWindow()
{
InitializeComponent();
XmlTree.Style = (Style)FindResource("TreeViewAllExpandedStyle");
_xmlDataProvider = FindResource("XmlData") as XmlDataProvider;
}
private void OpenXmlFile(string filePath)
{
XmlEditor.Clear(); // my text editor provided by AvalonEdit
XmlEditor.Load(filePath);
_xml = new XmlDocument();
_xml.Load(filePath);
if(_xmlDataProvider.Document == null)
_xmlDataProvider.Document = _xml;
}
private void saveChangesButton_Click(object sender, EventArgs e)
{
// some changes on _selectedElement (changes applies also into _xml)
_xml.Save(_tempFileName);
RefreshViews();
}
private void RefreshViews()
{
//
OpenXmlFile(_tempFileName);
// here I want to refresh my TreeView
// I noticed that when I select back my changed item, its values are set, but in my tree I see the old ones...
// I tried to do XmlTree.Focus() (nothing happens excepting control focus)
// and _xmlDataProvider.Refresh() (here comes NullReferenceException)
// I guess something bad happens in OpenXmlFile(...) method, because I reopen _xml and _xmlDataProvider looses a handler to it?
}
Could anyone explain why it doesn't work?
[edit]
I tried to modify 2 methods like this:
private void OpenXmlFile(string filePath)
{
XmlEditor.Clear();
XmlEditor.Load(filePath);
_xml = new XmlDocument();
_xml.Load(filePath);
_xmlDataProvider.Document = _xml;
}
private void saveChangesButton_Click(object sender, EventArgs e)
{
// ...
_xmlDataProvider.Document.Save(_tempFileName);
_xmlDataProvider.Refresh();
}
And now I get NullReferenceException while _xmlDataProvider.Refresh();
Hello you can try with Binding Two-Way on your TreeView and observable collection.
{Binding .... Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
Firslty you can modify your binding
ItemsSource="{Binding Source={StaticResource XmlData}, XPath=*, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
You are only setting the value of xmlDataProvider.Document in your OpenXmlFile() function if it is null. When you set
_xml = new XmlDocument();, it sets _xml to point to a new object, but xmlDataProvider is still pointing to the old object. Then, you have the following two lines:
if(_xmlDataProvider.Document == null)
_xmlDataProvider.Document = _xml;
If you're getting in here from RefreshViews(), _xmlDataProvider.Document will not be null, so you're never refreshing the XML file tied to your data provider.
what is the best practice to show text label (e.g. "Name") with the TextBox in WPF?
I want a label "Name" above the TextBox and many similar Labels/TextBoxes.
Should I put the pairs Label/TextBox into the vertical StackPanel?
Is there a simpler solution?
Here is a control that does it:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
public class KeyValueControl : Control
{
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
"Key",
typeof(string),
typeof(KeyValueControl),
new PropertyMetadata(default(string)));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(object),
typeof(KeyValueControl),
new FrameworkPropertyMetadata
{
DefaultValue = null,
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
});
static KeyValueControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(KeyValueControl), new FrameworkPropertyMetadata(typeof(KeyValueControl)));
}
public string Key
{
get
{
return (string)GetValue(KeyProperty);
}
set
{
SetValue(KeyProperty, value);
}
}
public object Value
{
get
{
return GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
}
Style:
<Style TargetType="{x:Type local:KeyValueControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:KeyValueControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Key, RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBox Grid.Column="1" Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage (create a property grid):
<ItemsControl>
<customControls:KeyValueControl Key="First" Value="{Binding Value1}" />
<customControls:KeyValueControl Key="Second" Value="{Binding Value2}" />
<customControls:KeyValueControl Key="Last" Value="{Binding Value3}" />
<customControls:KeyValueControl Key="Bool1" Value="{Binding Bool1}" Style="{StaticResource CheckBoxStyle}"/>
<customControls:KeyValueControl Key="Bool2" Value="{Binding Bool2}" Style="{StaticResource CheckBoxStyle}"/>
</ItemsControl>
It really depends on what you want to do with these controls in the future. If you want to reuse this kind of control multiple times (and maybe create it on the fly), it would be the best to create UserControl and program it. You can then easily reuse it in a very simple manner (like putting in on StackPanel).
Code for LabelTextBox.xaml
<UserControl x:Class="YourProject.LabelTextBox"
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="49" d:DesignWidth="314" MinHeight="49" MaxHeight="49">
<Grid>
<Label Content="Label" Height="28" HorizontalAlignment="Left" Name="BaseLabel" VerticalAlignment="Top" />
<TextBox Height="23" Margin="0,26,0,0" Name="BaseTextBox" VerticalAlignment="Top" />
</Grid>
</UserControl>
Code for LabelTextBox.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace YourProject
{
/// <summary>
/// Interaction logic for LabelTextBox.xaml
/// </summary>
public partial class LabelTextBox : UserControl
{
public LabelTextBox()
{
InitializeComponent();
}
string LocalLabel = "";
string LocalTextBox = "";
public string Label
{
get { return LocalLabel; }
set
{
LocalLabel = value;
BaseLabel.Content = value;
}
}
public string TextBox
{
get { return LocalTextBox; }
set
{
LocalTextBox = value;
BaseTextBox.Text = value;
}
}
}
}
You can change Label text and TextBox content with Label and TextBox property of new control (hidden in "Other" part of Properties in designer. You can also program additional functions for the UserControl.
If you don't need to reuse these controls so much, other solutions will suffice.
If you want the flexibility to manipulate this text label structure I suggest to wrap each TextBox and Label in a dock panel, and set the docking in a style that will apply on all labels and text boxes.
so it'll be like
<StackPanel>
<StackPanel.Resources>
<Style TargetType={x:Type Label}>
<Setter Property="DockPanel.Dock" Value="Top"/>
</Style>
</StackPanel.Resources>
<DockPanel>
<Label></Label>
<TextBox></TextBox>
</DockPanel>
<DockPanel>
<Label></Label>
<TextBox></TextBox>
</DockPanel>
</StackPanel>
I usually do something like this:
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Margin="5">Repository URL:</Label>
<TextBox Grid.Column="1" Margin="5"></TextBox>
</Grid>
</StackPanel>
If you did this often enough, you could create a UserControl or Datatemplate. But WPF is just a verbose markup language...
I use this
<Style TargetType="{x:Type TextBox}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="ControlLabel" Grid.Column="0"
VerticalAlignment="Top" HorizontalAlignment="Left"
Text="{TemplateBinding Tag}" />
<Line x:Name="LineColumnLabel" Grid.Column="1" X1="0" X2="0" Y1="0" Y2="{TemplateBinding Height}"
Stroke="#B31C1C1C" StrokeThickness="0.5" Margin="5,0,5,0" />
<ScrollViewer Grid.Column="2" x:Name="PART_ContentHost"
IsTabStop="{TemplateBinding ScrollViewer.IsTabStop}"
TextElement.Foreground="{TemplateBinding Foreground}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Tag" Value="{x:Null}">
<Setter TargetName="ControlLabel" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ControlLabel" Property="Margin" Value="0" />
<Setter TargetName="LineColumnLabel" Property="Visibility" Value="Collapsed" />
<Setter TargetName="LineColumnLabel" Property="Margin" Value="0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And
<Grid>
<TextBox Tag="The Name"/>
</Grid>
Create a class X that holds label and text that implements INotifyPropertyChanged. Make an ObservableCollection. This will be the ItemsSource for ListBox, ComboBox, StackPanel.. whatever you choose. Create a DataTemplate that displays X the way you want it to.