Im trying to achive something like here WPF DataGrid Grouping with sums and other fields to sum up 'quantity' property from items in a group. But it throws exception in foreach loop that it cant convert 'items' to my Type ('OrderItem' of which i thought items should be...)
C#
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is ReadOnlyObservableCollection<Object>)
{
var items = ((ReadOnlyObservableCollection<Object>) value);
Decimal total = 0;
foreach (OrderItem gi in items )
{
total += gi.quantity;
}
return total.ToString();
}
return "0";
}
XAML
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="5"/>
<TextBlock Text="Count" Margin="5" />
<TextBlock Text="{Binding Path=Items, Converter={StaticResource additionConverter}}" Margin="5"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
and resource
<local:AdditionConverter x:Key="additionConverter" />
how to get access to the base element of 'value'? I puted a breakpoint and it is there.
Sorry if i messed up something, i'm new to WPF.
Replace this :
foreach (OrderItem gi in items )
{
total += gi.quantity;
}
with :
foreach (CollectionViewGroup group in items)
{
foreach(OrderItem item in group.Items)
{
total += item.quantity;
}
}
and tell if this solves your problem.
Related
I have a class to color alternate the background of item, but if I delete a item, the background color does not update.
Is there a way to refresh the background color after deleting an item?
The code for alterante color.
class listview:
public class AlternatingRowListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listViewItem = element as ListViewItem;
if (listViewItem != null)
{
var index = IndexFromContainer(element);
if (index % 2 == 0)
{
listViewItem.Background = new SolidColorBrush(Colors.LightBlue);
}
else
{
listViewItem.Background = new SolidColorBrush(Colors.Transparent);
}
}
}
}
code xaml:
<local:AlternatingRowListView x:Name="listview">
<ListViewItem>item 1</ListViewItem>
<ListViewItem>item 2</ListViewItem>
<ListViewItem>item 3</ListViewItem>
<ListViewItem>item 4</ListViewItem>
<local:AlternatingRowListView.ItemTemplate>
<DataTemplate>
</DataTemplate>
</local:AlternatingRowListView.ItemTemplate>
</local:AlternatingRowListView>
Thanks in advance.
You just need to extend your already extended AlternatingRowListView control a bit to achieve what you need.
You can monitor whenever an item gets removed from the list by subscribing to the VectorChanged event of the Items, and then you just loop through all the already realized items below(visually) the removed item and change their background colors accordingly.
Something like this would do -
public AlternatingRowListView()
{
DefaultStyleKey = typeof(ListView);
Items.VectorChanged += OnItemsVectorChanged;
}
private void OnItemsVectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs args)
{
// When an item is removed from the list...
if (args.CollectionChange == CollectionChange.ItemRemoved)
{
var removedItemIndex = (int)args.Index;
// We don't really care about the items that are above the deleted one, so the starting index
// is the removed item's index.
for (var i = removedItemIndex; i < Items.Count; i++)
{
if (ContainerFromIndex(i) is ListViewItem listViewItem)
{
listViewItem.Background = i % 2 == 0 ?
new SolidColorBrush(Colors.LightBlue) : new SolidColorBrush(Colors.Transparent);
}
// If it's null, it means virtualization is on and starting from this index the container
// is not yet realized, so we can safely break the loop.
else
{
break;
}
}
}
}
if you are searching alternate odd/even row colors:
in app.xml
<Application
...
<Application.Resources>
<AlternateColorConverterx:Key="AlternateColorConverter" />
...
</Application.Resources>
generate a new class namely AlternateColorConverter.cs
(modify your hex colors.)
class AlternateColorConverter : IValueConverter
{
private static int idx_=-1;
private static int odd_=0;
private static int idx(int offset)
{
int idx_before = idx_;
idx_ = (idx_ + 1) % (offset);
if (idx_ < idx_before) odd_=(odd_+1)%2;
return odd_;
}
public static Color GetColorFromHex(string hexString)
{
Color x = (Color)XamlBindingHelper.ConvertValue(typeof(Color), hexString);
return x;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
int param = System.Convert.ToInt32(parameter);
return new SolidColorBrush(ColorUtils.GetColorFromHex((idx(param) == 0) ? "#F24C27" : "#FBBA42"));
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
and in yourpage.xml.cs :
(where ConverterParameter(offset) depends on column count. usually 2)
<ListView HorizontalAlignment="Stretch" Style="{StaticResource ListViewStyle}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="MaxHeight" Value="20"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Padding" Value="0"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="Models:YourObservableArray">
<Grid HorizontalAlignment="Stretch" Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180*"/>
<ColumnDefinition Width="100*"/>
</Grid.ColumnDefinitions>
<Border Background="{Binding Converter={StaticResource AlternateColorConverter},ConverterParameter=2}" HorizontalAlignment="Stretch" Grid.Column="0" Margin="0">
<TextBlock Text="{x:Bind Field1}" HorizontalAlignment="Stretch"/>
</Border>
<Border Background="{Binding Converter={StaticResource AlternateColorConverter},ConverterParameter=2}" HorizontalAlignment="Stretch" Grid.Column="0" Margin="0">
<TextBlock Text="{x:Bind Field2}" HorizontalAlignment="Stretch"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Hello i'm weary beginning in coding and this is my first serious application. I have data grid with grouping and I want to add in each group sum of values from column to make it to look like this
https://leeontech.files.wordpress.com/2010/02/final.png
I have try to use many solutions from internet but nothing work for me:
My XML
<DataGrid x:Name="GridRaport" CanUserAddRows="False" VerticalAlignment="Stretch" MinWidth="500" AlternatingRowBackground="LightBlue" AlternationCount="2" Margin="20,20,20,20">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel >
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,10,10,10" Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Margin="30,10,10,10" Text="{Binding ItemCount, StringFormat=Liczba wycieczek: {0}}" FontWeight="Bold" />
</StackPanel>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
And my Code behind
private void FillGridRaport()
{
string CmdString = string.Empty;
using (SqlConnection con = new SqlConnection(ConString))
{
CmdString = "Long query";
SqlCommand cmd = new SqlCommand(CmdString, con);
SqlDataAdapter sda = new SqlDataAdapter(cmd);
DataTable dt = new DataTable("Wycieczki");
sda.Fill(dt);
DataView dataView = dt.AsDataView();
BindingListCollectionView cv = CollectionViewSource.GetDefaultView(dataView) as BindingListCollectionView;
PropertyGroupDescription groupDescription1 = new PropertyGroupDescription();
groupDescription1.PropertyName = "Pracownik";
cv.GroupDescriptions.Add(groupDescription1);
GridRaport.ItemsSource = cv;
}
}
I will be weary grateful for your help
To get the sum, you need a converter. Make a class that implements IValueConverter, add it as a resource with a key and then reference it in the XAML.
Here is an example converter, I have it setup to take the field name as the ConverterParameter.
public class SumValues : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var cvg = value as CollectionViewGroup;
var field = parameter as string;
if (cvg == null || field == null)
return null;
// Double field
return cvg.Items.Sum(r => (double)(r as DataRowView)[field]);
// Or, if the field is nullable
//return cvg.Items.Sum(r => (r as DataRowView)[field] as double?); // "as" can return null so we have to use "double?"
// More complex example - string field that needs to be converted to a long
//return cvg.Items.Sum(r => long.Parse((r as DataRowView)[field].ToString()));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Add the converter as a resource, either in App.xaml or locally to the DataGrid:
<DataGrid.Resources>
<local:SumValues x:Key="SumValues" />
</DataGrid.Resources>
And, finally, add the textblock to your GroupItem's ControlTemplate:
<TextBlock Margin="60,10,10,10" Text="{Binding StringFormat=Sum: {0}, Converter={StaticResource SumValues}, ConverterParameter=MyDataSetFieldName}" FontWeight="Bold" />
i'm trying to bind a name of a file given by a filepath to a TextBlock. The filepath is stored in a list which is bound to the ItemsSourceProperty of a ListBox. The TextBlock is set as DataTemplate.
My question is: How can i get the name without path and extension and bind it to the TextBlock?
The XAML code for better explanation:
<ListBox Name="MyListBox" Margin="2">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the code behind:
string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
List<string> PathList = Directory.GetFiles(path, "*.txt").ToList();
Binding myBind = new Binding();
myBind.Source = PathList;
myListBox.SetBinding(ListBox.ItemsSourceProperty, myBind);
One uses a converter to change the text of a selected listbox item which is fully pathed to just the filename.
In the following example there is a list and a textbox next to it. Once an item is selected, the textbox bound to the list's SelectedItem extracts the pathed string which is passed to a converter which returns just the filename to show.
Example
XAML
<Window x:Class="WPFStack.ListBoxQuestions"
xmlns:local="clr-namespace:WPFStack"
xmlns:converters="clr-namespace:WPFStack.Converters"
.../>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<converters:PathToFilenameConverter x:Key="FilenameConverter" />
<x:Array x:Key="FileNames" Type="system:String">
<system:String>C:\Temp\Alpha.txt</system:String>
<system:String>C:\Temp\Beta.txt</system:String>
</x:Array>
</StackPanel.Resources>
<ListBox Name="lbFiles"
ItemsSource="{StaticResource FileNames}" />
<TextBlock Text="{Binding SelectedItem,
ElementName=lbFiles,
Converter={StaticResource FilenameConverter}}"
Margin="6,0,0,0" />
</StackPanel>
Converter
namespace WPFStack.Converters
{
public class PathToFilenameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
object result = null;
if (value != null)
{
var path = value.ToString();
if (string.IsNullOrWhiteSpace(path) == false)
result = Path.GetFileNameWithoutExtension(path);
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
ItemTemplate Use of Converter
The converter is reused in the template as such
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource FilenameConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
If you just want to add a static list to list box you should do it like this.
XAML:
<ListBox x:Name="lb" ItemsSource="{Binding Collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind constructor for in the main window:
public MainWindow()
{
InitializeComponent();
List<string> l = new List<string>();
l.Add("string path 1");
l.Add("string path 2");
l.Add("string path 3");
l.Add("string path 4");
lb.ItemsSource = l;
}
You should be aware that there is a much better way of doing these things. I would honestly suggest you go look MVVM and doing a proper binding to a ViewModel.
I am new in WPF I want validate my IP address but I have a problem: when I try to show the error message, it shows me only an empty red border.
Here is the ControlTemplate and all the code:
<Window x:Class="SOTCBindingValidation.Window1"
x:Name="This"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SOTCBindingValidation"
Title="SOTC Validation Test" Height="150" Width="400">
<Window.Resources>
<local:ErrorsToMessageConverter x:Key="eToMConverter"/>
<ControlTemplate x:Key="customvalidatortemplate">
<StackPanel Orientation="Horizontal">
<Border BorderThickness="1" BorderBrush="Red" VerticalAlignment="Top">
<Grid>
<AdornedElementPlaceholder x:Name="adorner" Margin="-1"/>
</Grid>
</Border>
<Border x:Name="errorBorder" Background="Red" Margin="8,0,0,0"
CornerRadius="0" IsHitTestVisible="False">
<TextBlock Text="{Binding ElementName=AddressBox,
Path=(Validation.Errors),
Converter={StaticResource eToMConverter}}"
Foreground="White" FontFamily="Segoe UI"
Margin="8,2,8,3" TextWrapping="Wrap"
VerticalAlignment="Center"/>
</Border>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<StackPanel Margin="5">
<TextBlock Margin="2">Enter An IPv4 Address:</TextBlock>
<TextBox x:Name="AddressBox"
Validation.ErrorTemplate="{StaticResource customvalidatortemplate}"
Margin="0,0,235.5,0">
<TextBox.Text>
<Binding ElementName="This" Path="IPAddress"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IPv4ValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</Window>
ErrorsToMessageConverter.cs file :
public class ErrorsToMessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var sb = new StringBuilder();
var errors = value as ReadOnlyCollection<ValidationError>;
if (errors != null)
{
foreach (var e in errors.Where(e => e.ErrorContent != null))
{
sb.AppendLine(e.ErrorContent.ToString());
}
}
return sb.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
IPv4ValidationRule.cs file :
public class IPv4ValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var str = value as string;
if (String.IsNullOrEmpty(str))
{
return new ValidationResult(false,
"Please enter an IP Address.");
}
var parts = str.Split('.');
if (parts.Length != 4)
{
return new ValidationResult(false,
"IP Address should be four octets, seperated by decimals.");
}
foreach (var p in parts)
{
int intPart;
if (!int.TryParse(p, NumberStyles.Integer, cultureInfo.NumberFormat, out intPart))
{
return new ValidationResult(false,
"Each octet of an IP Address should be a number.");
}
if (intPart < 0 || intPart > 255)
{
return new ValidationResult(false,
"Each octet of an IP Address should be between 0 and 255.");
}
}
return new ValidationResult(true, null);
}
}
I've found the solution (after a sleep:). In fact the exact element source you have to bind to can be accessed via the AdornedElementPlaceholder. It has a property called AdornedElement, TemplateBinding does not work in this case because TemplatedParent does not point to the TextBox, it's just another Control which is used for ErrorTemplate control. So the code should be like this:
<TextBlock Text="{Binding ElementName=adorner,
Path=AdornedElement.(Validation.Errors),
Converter={StaticResource eToMConverter}}"
Foreground="White" FontFamily="Segoe UI" Margin="8,2,8,3"
TextWrapping="Wrap" VerticalAlignment="Center"/>
Note about how we set the attached property Validation.Errors for the AdornedElement. Also note about the name adorner which is exactly the name you set for the AdornedElementPlaceholder. I've made a demo and surely it should work.
I am using IValueconverter interface to change the tooltip text of an image.
The tool tip should change based on label.
<Label Content="9898980001" Height="28" HorizontalAlignment="Left" Margin="1733,231,0,0" Name="lbl02scanning" VerticalAlignment="Top" Foreground="Blue" >
<Image Height="49" HorizontalAlignment="Right" Margin="0,131,113,0"
Name="img02scanning"
Source="/TEST;component/Images/LoadingStation.png" Stretch="Fill"
VerticalAlignment="Top" Width="30" Cursor="Hand">
<Image.ToolTip>
<StackPanel Background="AliceBlue">
<TextBlock Padding="5" Foreground="White" MinHeight="20"
Background="Blue" FontWeight="Bold"
Text="Scanning Station" />
<StackPanel Orientation="Horizontal">
<Image
Source="pack://application:,,,/TEST;component/Images/coilonsaddle_large.png"
Height="100" Width="100" />
<TextBlock Padding="10" TextWrapping="WrapWithOverflow"
MaxWidth="200" Background="AliceBlue"
Foreground="Black" FontWeight="Bold"
Text="{Binding ElementName=lbl02scanning, Path=Name,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
</StackPanel>
<TextBlock Padding="5" Foreground="White" MinHeight="20"
Background="Blue" FontWeight="Bold"
Text="Report to admin in case of coil location mismatch"/>
</StackPanel>
</Image.ToolTip>
</Image>
The converter class:
public class FormatterForCoilToolTip : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(parameter.ToString() == "02")
{
return value.ToString() + " Startin";
}
else
{
return value.ToString() + " Finishing";
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The tooltip's Textblock content is not changing. But if i change to:
Text="{Binding ConverterParameter=255, Converter={StaticResource FormatterFOrCoilToolTip}}
then it is working. But i want to pass the lbl02scanning text value. Why it is not working??
First of all you should bind to Content property and not Name property in case you want Text of Label.
Most importantly Tooltip does not lies in same Visual Tree as that of label, hence binding with elementName won't work. However, you can use x:Reference to get the element even if it doesn't exist in same Visual Tree.
Text="{Binding Source={x:Reference lbl02scanning}, Path=Content,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
Note - x:Reference is introduced in WPF 4.0. If you are using WPF 3.5 you can't use this.
Update for error - service provider is missing the name resolver service
Just found out bug is reported at Microsoft site that x:Reference fails in case Target is Label. However, i couldn't reproduce this issue at my end since i have WPF 4.5 installed at my end and i guess they have fixed the issue in future version.
In case you target WPF 4.0, i would advise you to use TextBlock in place of Label:
<TextBlock Text="9898980001" Height="28" HorizontalAlignment="Left"
Margin="1733,231,0,0" Name="lbl02scanning" VerticalAlignment="Top"
Foreground="Blue" />
and then bind with Text property instead of Content.
Text="{Binding Source={x:Reference lbl02scanning}, Path=Text,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
Either, you can refer to workaround provide under workarounds section here.
You can override the ProvideValue method of the Reference class and skip the reference search login in design time:
[ContentProperty("Name")]
public class Reference : System.Windows.Markup.Reference
{
public Reference()
: base()
{ }
public Reference(string name)
: base(name)
{ }
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget valueTargetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueTargetProvider != null)
{
DependencyObject targetObject = valueTargetProvider.TargetObject as DependencyObject;
if (targetObject != null && DesignerProperties.GetIsInDesignMode(targetObject))
{
return null;
}
}
return base.ProvideValue(serviceProvider);
}
Update with another workaround
This will work for all versions WPF 3.5, WPf 4.0 and WPF 4.5.
First of all bind Image Tag with content of label.
Second host your stackPanel inside ToolTip control so that you can
take benefit of PlacementTarget property.
Third bind with PlacementTarget.Tag of Tooltip.
Relevant code will look like this:
<Image Tag="{Binding ElementName=lbl02scanning,Path=Content}">
<Image.ToolTip>
<ToolTip>
<TextBlock Text="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ToolTip},
Path=PlacementTarget.Tag,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
</ToolTip>
</Image.ToolTip>
</Image>
Also you need to update converter code to put null check over there since PlacementTarget will be null until you open tooltip.
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value != null)
{
if (parameter.ToString() == "02")
{
return value.ToString() + " Starting";
}
else
{
return value.ToString() + " Finishing";
}
}
return String.Empty;
}
Try This
Text="{Binding Path=Content,ElementName=lbl02scanning, ConverterParameter=255, Converter={StaticResource FormatterFOrCoilToolTip}}