Displaying amount of "filled" rows in group header WPF - c#

I have a certain amount of rows in my ViewModel observablecollection that are being grouped and displayed in a datagrid.
<CollectionViewSource x:Key="ColViewSource" Source="{Binding Collection}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Cat"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
My rows contain a textfield and a threestate checkbox that needs to be true or indeterminate state for the row to be considered "filled" (default false). I would like to display the amount of "filled" rows in my groupheader out of total items in group. For example if I have a group with 5 items and the user ticked 2 of the checkboxes I would like to see 2 out of 5 or 2 / 5 in group header. I can manage to get the name and itemcount without a problem to display in groupheader but Im stuck on displaying the amount of filled rows. Here is how I would like my group header to look.
<Expander.Header>
<StackPanel Orientation="Horizontal" Height="50">
<TextBlock Text="{Binding Name}"/>
implement this-->
<TextBlock Text = amount of items currently filled.
<TextBlock Text="{Binding ItemCount}"/>
</StackPanel>
</Expander.Header>
I have implemented propertychanged for both my collection and items. Im guessing it would require some sort of converter , can someone point me in the right direction?

According to this article, Your group style must look like this in Xaml:
<local:PercentageConverterx:Key="percentageConverter" />
//...
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Converter={StaticResource percentageConverter} }"/>
<TextBlock Text="{Binding ItemCount}"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
in which
public class PercentageConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
CollectionViewGroup cvg = value as CollectionViewGroup;
int count = 0;
int check = 0;
foreach (Item t in cvg.Items)
{
count++;
if (t.IsCheck== true)
check++;
}
return (check / (double)count).ToString("0.00") + "%";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
Note that I assumed CollectionViewSource.Source property is set to a ObservableCollection in which Item is like this:
public class Item
{
public string Name {get; set; }
public bool? IsChecked { get; set; }
}
which is bound to the threestate CheckBox.

Related

Able to add Text to a textblock after binding?

so i've a Textblock that binds a property using the (Text="x"), but is there a way to add more text to it?
So this is the line in XAML
<TextBlock Name="UpdatedStock" FontSize="12" Text="{x:Bind Stock, Mode=TwoWay}" HorizontalAlignment="Left" />
and it only says the number of Stock atm, but i want it to say "X in stock". But i cant add more text to it, is there a way somehow to add it on the same line or i have to do it somewhere else?
Kinds regards
Able to add Text to a textblock after binding?
You have many ways to approach. In general we often add new TextBlock with static text X and place left of UpdatedStock
<RelativePanel>
<TextBlock
x:Name="UpdatedStock"
FontSize="12"
RelativePanel.AlignLeftWithPanel="True"
Text="{x:Bind Stock, Mode=OneWay}" />
<TextBlock Margin="2,0,0,0"
FontSize="12"
RelativePanel.RightOf="UpdatedStock"
Text="X" />
</RelativePanel>
And the other way is use IValueConverter to append X to Stock propety.
public class StringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return null;
return value.ToString()+ "X";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Usage
<Page.Resources>
<local:StringConverter x:Key="StrConverter" />
</Page.Resources>
<Grid>
<TextBlock
x:Name="UpdatedStock"
FontSize="12"
RelativePanel.AlignLeftWithPanel="True"
Text="{x:Bind Stock, Mode=OneWay, Converter={StaticResource StrConverter}}" />
</Grid>

WPF - Conditional binding in ItemsControl

I'm very new to WPF, and am just getting started with data binding. What I'd like to do is generate a list of checkboxes based on a list in my view model. The XAML I have at the moment is:
<ItemsControl ItemsSource="{Binding Path=TestList, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Name}" IsChecked="{Binding Path=Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10,5,10,5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This works correctly, and generates a checkbox for every item in TestList. What I'd like to do is only generate checkboxes for items where the condition TestList[i].Type == "Mode" is true. I believe that I may need to use a <DataTrigger> element, but I don't know the details of how to do this.
[EDIT] Just to clarify, each element of TestList has Name, Enabled, and Type properties.
There are several ways to do this. However, the simplest static approach would be to just filter it at your ViewModel
Filtered = new ObservableCollection(TestList.Where(x => x.Type == "Mode"));
...
<ItemsControl ItemsSource="{Binding Path=Filtered , UpdateSourceTrigger=PropertyChanged}">
Note : There are fancier more dynamic ways to achieve this, though this might help you out
As I guess that you want to make the checkbox appear if TestList.Type changes, I would suggest make a Converter and Bind it to the CheckBox Visibility.
public sealed class CheckBoxVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return Visibility.Visible;
var type = (string)value;
var condition = (string)parameter;
return type.Equals(condition) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
And then in the dictionary add the reference to your namespace
xmlns:converters="clr-namespace:Projct.Converters;
and in the resources dictionary
<converters:CheckBoxVisibilityConverter x:Key="CheckBoxConverter"/>
Finally in the xaml
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox
Margin="10,5,10,5"
Content="{Binding Path=Name}"
IsChecked="{Binding Path=Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding Path=Type, Converter={StaticResource CheckBoxConverter}, ConverterParameter=Mode}" />
</DataTemplate>
</ItemsControl.ItemTemplate>

WPF ComboBox in Datagrid throws error if Binding is NULL

I have a DataGrid I want to edit. One column is a Combobox
<DataGrid ItemsSource="{Binding Persons, Mode=TwoWay}"
SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Company" >
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.Companies}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Company.Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The problem is if the Company is null then I get the error
Two-way binding requires Path or XPath
How can I get around that to allow the Company be set to null?
Text="{Binding Company.Name}"always requires Companyto be a valid object because the system wants to finally access the Name property. I still have some hope you can create a dummy Company object and set the Name to an empty string. Otherwise a converter helps:
<DataGrid ItemsSource="{Binding Persons, Mode=TwoWay}"
SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" >
<DataGrid.Resources>
<local:MyConverter x:Key="MyConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Company" >
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.Companies}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Company, Converter={StaticResource MyConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public class MyConverter : IValueConverter
{
public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return string.Empty;
}
Type myType = value.GetType ();
PropertyInfo pinfo = myType.GetProperty ("Name");
if (pinfo == null)
{
return string.Empty;
}
return (string)pinfo.GetValue(value);
}
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
The converter uses reflexion to access the Name property. If the type of Companyis accessible for the converter (and the logic allows it) you can of course use is directly.
The question already has a correct answer, so this is just a quick follow up.
Let's assume a demo ViewModel:
public MyViewModel()
{
CompanyType companyA = new CompanyType();
companyA.Name = "Comp. A";
CompanyType companyB = new CompanyType();
companyB.Name = "Comp. B";
Companies.Add(companyA);
Companies.Add(companyB);
PersonType person1 = new PersonType();
person1.Id = 1;
person1.Company = null;
PersonType person2 = new PersonType();
person2.Id = 2;
person2.Company = companyB;
persons.Add(person1);
persons.Add(person2);
}
Now, what is not working in the ComboBox is that it is not changing the value of a Person's Company after a new value of a different Company is selected from the ComboBox: in the demo let's say we want assign both persons1 and 2 to companyA from the UI.
Furthermore CompanyB is not selected in the ComboBox of person2, if we don't bind the SelectedItem of the ComboBox to the Company property of the PersonType.
Here you find the fix to the Xaml of the Combobox
<DataTemplate>
<ComboBox
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedItem="{Binding Company}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.Companies}"/>
</DataTemplate>
The classes of my demo
public class PersonType // don't need to notify below
{
public int Id { get; set; }
public CompanyType Company { get; set; }
}
public class CompanyType
{
public string Name { get; set; }
}
Please notice also that, due to the binding of Combobox SelectedItem, person2 has its ComboBox item CompanyB selected in the beginning and now it is possible to change one's Company to CompanyA, for example.

XAML ListBox - How to change style of last item only?

I have a RadDataBoundListBox (from Telerik) that represents the items of a List. Each item is separated by a bottom line x:Name="ItemSeparatorBorder". The ListBox itself has a header and a footer containing a line too (x:Name="ListTopBorder"and x:Name="ListBottomBorder"). Now I need a way to disable the line (x:Name="ItemSeparatorBorder") of the last item in this ListBox.
I thought about some Visibility binding to x:Name="ItemSeparatorBorder" with a Converter that matches the index of the current item to the total count of the ListBox. But I don't know how to implement it and I can't find any good sample.
The code should work on Windows Phone 8.0 / .NET 4.0.
This is my code so far:
<telerikPrimitives:RadDataBoundListBox
x:Name="ListBox"
ItemsSource="{Binding Items}">
<telerikPrimitives:RadDataBoundListBox.ListHeaderTemplate>
<DataTemplate>
<Grid Height="30">
<Border
x:Name="ListTopBorder"
Height="1"
VerticalAlignment="Bottom"
Background="Blue"/>
</Grid>
</DataTemplate>
</telerikPrimitives:RadDataBoundListBox.ListHeaderTemplate>
<telerikPrimitives:RadDataBoundListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="1"></RowDefinition>
</Grid.RowDefinitions>
<controls:ListItem Margin="30,10,0,10">/>
<Border
x:Name="ItemSeparatorBorder"
Grid.Row="1"
Height="1"
Background="Blue"
Margin="30,0,0,0"/>
</Grid>
</DataTemplate>
</telerikPrimitives:RadDataBoundListBox.ItemTemplate>
<telerikPrimitives:RadDataBoundListBox.ListFooterTemplate>
<DataTemplate>
<Grid Height="30">
<Border
x:Name="ListBottomBorder"
Height="1"
VerticalAlignment="Top"
Background="Blue"/>
</Grid>
</DataTemplate>
</telerikPrimitives:RadDataBoundListBox.ListFooterTemplate>
</telerikPrimitives:RadDataBoundListBox>
How can I hide the Border of the last item?
To make it more clear, I want to remove the last blue line here:
I think the best approach is to use an IValueConverter that gets a reference to the items collection. Make a converter that inherits DependencyObject, and give it a property for the source list. Then you can check the current item index:
public class ItemToVisibilityConverter : DependencyObject, IValueConverter
{
public IList Items
{
get { return (IList )GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty=
DependencyProperty.Register("Items", typeof(IList), typeof(ItemToVisibilityConverter), new PropertyMetadata(null));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool hide = Items != null
&& value != null
&& Items.IndexOf(value) == Items.Count - 1;
return (hide ? Visibility.Collapsed : Visibility.Visible);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException;
}
}
The put a converter instance in the control's resources:
<telerikPrimitives:RadDataBoundListBox
x:Name="ListBox"
ItemsSource="{Binding Items}">
<telerikPrimitives:RadDataBoundListBox.Resources>
<ResourceDictionary>
<local:ItemToVisibilityConverter x:Key="ItemToVisibilityConverter"
Items="{Binding Items}" />
</ResourceDictionary>
</telerikPrimitives:RadDataBoundListBox.Resources>
...
</telerikPrimitives:RadDataBoundListBox>
Finally, in the "ItemTemplate", bind to the current item using the converter:
<Border x:Name="ItemSeparatorBorder"
Visibility="{Binding Converter={StaticResource ItemToVisibilityConverter}}"
... />

Winrt IValueConverter on a GridView item

I have a ObservableCollection<TimeSpan> Laps which I am databinding to a gridview. This works as expected but I need to apply a converter to set the format of the TimeSpan:
In my resources:
<utils:TimeToStringConverter x:Key="myConverter"/>
My Gridview:
<GridView HorizontalAlignment="Left" Height="278" Margin="78,220,0,0" VerticalAlignment="Top" Width="1278" ItemsSource="{Binding model.Laps}" />
I have the following converter which I want to apply on the items of a GridView / ListView in Winrt:
public class TimeToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
TimeSpan t = (TimeSpan) value;
return t.ToString(#"hh\:dd\:ss\.fff");
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
I can't figure out how to get the converter to work, and when I apply it on the GridView then it is looking for me to convert an Observable collection rather than just a TimeSpan item. What should I do here ?
Regards
You need something like a
<GridView
...>
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Converter={StaticResource myConverter}}" />
</DataTemplate>
</GridView.ItemTemplate>
Use the below modified line
I've just modified the item source like below
ItemsSource="{Binding model.Laps,Converter={StaticResource myConverter}}"
<GridView HorizontalAlignment="Left" Height="278" Margin="78,220,0,0" VerticalAlignment="Top" Width="1278" ItemsSource="{Binding model.Laps,Converter={StaticResource myConverter}}" />

Categories

Resources