WPF binding from parent to child element - c#

Here is my XAML
<ListView x:Name="missingVariablesListView" ScrollViewer.CanContentScroll="True" HorizontalAlignment="Left" Height="320" Margin="81,28,0,0" VerticalAlignment="Top" Width="641" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="ComponentID: " FontWeight="Bold" Foreground="Brown" />
<TextBlock Text="{Binding Name}"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Variable Name: " Foreground="Green"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text=" "/>
<TextBlock Text="Variable Value: " Foreground="Blue"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style >
<Style.Triggers>
<DataTrigger Binding="{Binding IsMissing}" Value="false">
<Setter Property="UIElement.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<TextBlock Text="-----------------------------------------------------------------"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is the CodeBehind
this.missingVariablesListView.DataContext = //Code to fill in the list View
Classes Involved
public class Component
{
private ObservableCollection<ComponentParameter> parameters = new ObservableCollection<ComponentParameter>();
public string Name
{
get;
set;
}
public ObservableCollection<ComponentParameter> Parameters
{
get{return parameters;}
set{parameters = value;}
}
}
public class ComponentParameter
{
public string Name
{
get;set;
}
public string Value
{
get;set;
}
public bool HasErrors
{
get;
set;
}
public bool IsMissing
{
get;set;
}
Sample Output (at the moment)
ComponentID: Component1
--------------------------
ComponentID: Component2
VariableName:Var1 Variable Value:Val1
VariableName:Var2 Variable Value:Val2
-----------------------
ComponentID: Component3
-----------------------
ComponentID: Component4
-----------------------
What i want to do is that whenever the boolean IsMissing is true for the inner element within the itemsControl i want to make sure that the ComponentID and the Name properties within the StackPanel (that has the orientation as horizontal) are not shown in the window including the child elements. Basically i am trying to find a way to exclude the whole description for that particular ComponentID whose isMissing variable is set to true. Any suggestions on this?

I would consider to add a property IsAnyParameterMissing to the Component class:
public class Component
{
private ObservableCollection<ComponentParameter> parameters = new ObservableCollection<ComponentParameter>();
public string Name
{
get;
set;
}
public ObservableCollection<ComponentParameter> Parameters
{
get{return parameters;}
set{parameters = value;}
}
public bool IsAnyParameterMissing
{
get { return this.Parameters.Any(param => param.IsMissing); }
}
}
And then bind the visibility to this property:
<StackPanel Orientation="Horizontal" Visibility="{Binding IsAnyParameterMissing, Converter={BooleanToVisibilityConverter}}">
<TextBlock Text="ComponentID: " FontWeight="Bold" Foreground="Brown" />
<TextBlock Text="{Binding Name}"/>
</StackPanel>
This will only display the StackPanel if any of the Parameters in the collection has its IsMissing property equal to true. Note that this will not change the visibility if the IsMissing property is changed for any of the items! This would need some additional work.
EDIT: Note that the {BooleanToVisibilityConverter} might need to be adjusted, depending on your available converters. Should be a simple task though.

One thing i noticed is that you don't implement the properties (such as "isMissing") as DependencyProperty. Neither do you use property change notifications via INotifyPropertyChanged.
You have to implement either of the two, otherwise changes of the properties will not be propagated through the bindings. That means, the trigger wouldn't trigger...

You could use a BooleanToVisibilityConverter to hide those items for you, although you may need to change your property or add a new IsPresent property for this:
In Resources:
<Converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"
IsInverted="True" />
In your ListView.ItemTemplate:
<StackPanel Orientation="Horizontal" Visibility="{Binding IsMissing, Converter={
StaticResource BoolToVisibilityConverter}}">
<TextBlock Text="ComponentID: " FontWeight="Bold" Foreground="Brown" />
<TextBlock Text="{Binding Name}"/>
</StackPanel>
Custom BoolToVisibilityConverter with IsInverted property:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolToVisibilityConverter : IValueConverter
{
public bool IsInverted { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value.GetType() != typeof(bool)) return null;
bool boolValue = IsInverted ? !(bool)value : (bool)value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value.GetType() != typeof(Visibility)) return null;
if (IsInverted) return (Visibility)value != Visibility.Visible;
return (Visibility)value == Visibility.Visible;
}
}
Now it will just take you one minute to implement it. I have updated the XAML example above to use it.

Related

How to bind property of child class from itemscontrol of parent class

I have an items control binded to list of parent class.
I need to bind the properties of the child class in the data template.
These are the classes I have
public class Parent {
private string _name;
public string Name
{
get { return _name; }
set
{
if (Equals(value, _name)) return;
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public class Child1 : Parent{
private string _prob1;
public string Prob1
{
get { return _prob1; }
set
{
if (Equals(value, _prob1)) return;
_prob1= value;
OnPropertyChanged(nameof(Prob1));
}
}
}
public class Child2 : Parent{
private string _prob2;
public string Prob2
{
get { return _prob2; }
set
{
if (Equals(value, _prob2)) return;
_prob2= value;
OnPropertyChanged(nameof(Prob2));
}
}
}
and in my view model I have an observable collection of Parent class
public ObservableCollection<Parent> ParentList { get; set; }
and my xaml code
<ItemsControl ItemsSource="{Binding ParentList }">
<ItemsControl.ItemTemplate>
<DataTemplate
DataType="domainObject:Child1">
<TextBlock Text="{Binding Name}" Margin="5" IsEnabled="False" HorizontalAlignment="Center" Height="22" Background="Transparent" />
<TextBlock Text="{Binding Prob1}" Margin="5"HorizontalAlignment="Center" Height="22" />
I want to bind the 2. textbox to a property of child class.
Is there any simple way to fix this problem?
In case the ParentList collection contains elements of different derived types (either Child1 or Child2) you should have different DataTemplates, which would automatically be chosen by their DataType property.
You may declare these DataTemplates in the Resources of the ItemsControl:
<ItemsControl ItemsSource="{Binding ParentList}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type domainObject:Child1}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" ... />
<TextBlock Text="{Binding Prob1}" ... />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type domainObject:Child2}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" ... />
<TextBlock Text="{Binding Prob2}" ... />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>

Combobox showing error "Binding expression path error"

I have two combobox which are currently binded to the user model. The first combobox supposed to show the current userRole value before clicking on the combobox. The other combobox supposed to show the userStatus either 1 or 0. Now the second combobox is not displaying any value. However the first combobox is displaying the value once its clicked only.
Here is the xaml code:
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="12" Text="User Role: " VerticalAlignment="Center" />
<ComboBox x:Name="cbUserRole" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue" HorizontalAlignment="Stretch" VerticalAlignment="Center" Loaded="cbUserRole_Loaded" SelectedItem="{Binding UserRole, Mode=TwoWay" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="12" Text="User Status: " VerticalAlignment="Center" />
<ComboBox x:Name="cbUserStatus" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue" HorizontalAlignment="Stretch" VerticalAlignment="Center" SelectedIndex="{Binding UserStatus, Converter={StaticResource boolToIndexConverter}}" />
</StackPanel>
Here is my converter code:
public class BoolToIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value == true) ? 0 : 1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int)value == 0) ? true : false;
}
}
The code above is refered from this link:
Here is my User model code:
private string userrole;
public string UserRole
{
get { return userrole; }
set
{
userrole = value;
OnPropertyChanged("UserRole");
}
}
private bool userstatus;
public bool UserStatus
{
get { return userstatus; }
set
{
userstatus = value;
OnPropertyChanged("UserStatus");
}
}
How can i fix this problems? I did search and tried from different blogs but its not working for me.
The problem is that your combobox's DataContext is User, but you need to set ItemsSource from DataGrid's datacontext. In order to do that you need to use the following syntax:
<ComboBox
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}}, Path=DataContext.Users}"
x:Name="cbUserRole" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue" HorizontalAlignment="Stretch" VerticalAlignment="Center"
SelectedItem="{Binding UserRole,Mode=TwoWay}"></ComboBox>
And Remove Loaded handler.
You can also use <DataGrid DataContext="{StaticResource uvm}" instead of codebehind code:
var userList = new UserViewModel().Users;
userDataGrid.ItemsSource = userList;
You don't have ItemsSource for your Status Combobox. I think you should add collection of statuses to UserViewModel and bind it like in the example above.

Getting SQL/Linq-query variable to display 3 decimals for multi-type-variable items in one ListBox

Good day
Wonder if anyone can help.
I intend to display the rows containing the items for the following
public class Product
{
[PrimaryKey, AutoIncrement]
public int ChemID { get; set; }
public string ChemCat { get; set; }
public string ChemName { get; set; }
public double ChemWeight { get; set; }
}
in the form of Xaml ListBox
<ListBox Name="listChems" Height="200" Width="300" HorizontalAlignment="Left" Margin="0,5,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="18" VerticalAlignment="Top" >
<TextBlock Text="{Binding ChemCat}" Margin="10,0,0,0" Width="90" FontSize="14"/>
<TextBlock Text="{Binding ChemName}" Margin="10,0,0,0" Width="90" FontSize="14"/>
<TextBlock Text="{Binding ChemWeight}" Margin="10,0,0,0" Width="60" FontSize="14"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
using the usual SQL/Linq query
private void btnDisplayAll_Click(object sender, RoutedEventArgs e)
{
var display = conn.Table<Product>()
.Select(g => g);
listChems.ItemsSource = display;
}
ChemCat and ChemName are purely strings eg. Lanthanide and Neodymium respectively.
However, I need to have ChemWeight (type double) to be displayed in 3 decimals, even if the user provide a number that has 0 or 1 or 2 decimals, ie.
1 to be displayed as 1.000
1.4 to be displayed as 1.400
1.42 to be displayed as 1.420
How do I get SQL/Linq-query variable 'display' to force {Binding ChemWeight} to display 3 decimals, considering that the other members ChemCat and ChemName are strings and they are all tied together in a one ListBox.
(Earlier post said ChemPrice, it was meant to be ChemWeight)
Many thanks.
For WPF
Use the StringFormat property in the binding.
<TextBlock Text="{Binding ChemPrice, StringFormat={}{0:0.000}}" Margin="10,0,0,0" Width="60" FontSize="14"/>
For WINRT
Use a IValueConverter
public class StringFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return string.Format((string)parameter, value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Note that you need to specify your converter for your control or window.
<UserControl.Resources>
<local:StringFormatConverter x:Key="Converter"></local:StringFormatConverter>
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding ChemPrice, Converter={StaticResource Converter}, ConverterParameter=\{0:0.00\}}" Margin="10,0,0,0" Width="60" FontSize="14"/>
</Grid>

GroupStyle Xaml visibility issue on windows phone 8.1

I just need a pair of extra eyes to see what i am doing wrong here.
I have a xaml page with a groupstyle inside a list view. here is how it looks,
<GroupStyle
HeaderContainerStyle="{StaticResource JumpListListHeaderContainerStyle}"
HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
BorderThickness="0,0,0,1"
BorderBrush="{StaticResource RedBrush}"
Margin="0,0,0,9.5">
<TextBlock Text="{Binding Key}"
Foreground="{StaticResource RedBrush}"
FontSize="23"
Visibility="{Binding IsGroupheaderVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
FontFamily="Segoe WP Semibold"
OpticalMarginAlignment="TrimSideBearings"
VerticalAlignment="Bottom" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
What i need do is hide the groupstule header based on a condition in the viewmodel. Here's the property.
private bool _isGroupHeaderVisibile;
public bool IsGroupheaderVisible
{
get { return _isGroupHeaderVisibile; }
set
{
if (value == _isGroupHeaderVisibile) return;
_isGroupHeaderVisibile = value;
NotifyOfPropertyChange(() => IsGroupheaderVisible);
}
}
and on the OnInitialize event i am setting it to true or false based on some condition. But unfortunately it does not hide/show but always shows up.
Here's the BooleanToVisibilityConverter that i am using,
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool flag;
return value != null && bool.TryParse(value.ToString(), out flag) && flag
? Visibility.Visible
: Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value is Visibility && (Visibility)value == Visibility.Visible;
}
}
NOTE: I am using CaliburnMicro, so i have methods like OnInitialize() which i am omitting.

WPF DataGrid Grouping with sums and other fields

I have a DataGrid that is bound to collection and that I want to be grouped. Here is the code
Collection:
private string _ID;
private string _Descript;
private decimal _Amount;
public string ID
{
get { return _ID; }
set { _ID = value; NotifyPropertyChanged("ID"); }
}
public decimal Amount
{
get { return _Amount; }
set { _Amount = value; NotifyPropertyChanged("Amount"); }
}
public string Descript
{
get { return _Descript; }
set { _Descript = value; NotifyPropertyChanged("Descript"); }
}
C#;
ListCollectionView groupcollection = new ListCollectionView(myCollection);
groupcollection.GroupDescriptions.Add(new PropertyGroupDescription("ID"));
myDataGrid.ItemsSource = groupcollection;
XAML:
<DataGrid Name="myDataGrid">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<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=ItemCount}" Margin="5"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
This works perfectly but now in the Expander.Header I want to added a summary of a "Amount" and "Descript" value. So for example if there were 3 records in the collection with ID "ABC" each one being 20 and the description for ABC being "My Count" I would want to see;
ABC My Count total 60
How would I do that?
You could use a converter that's passed the Items property of the group header e.g.
<Window.Resources>
<local:GroupsToTotalConverter x:Key="groupsConverter" />
</Window.Resources>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="5"/>
<TextBlock Text="total" Margin="5" />
<TextBlock Text="{Binding Path=Items, Converter={StaticResource groupsConverter}}" Margin="5" />
</StackPanel>
where the converter performs the calculation and passes back the total as the string for the text block:
public class GroupsToTotalConverter : IValueConverter
{
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 (GroupItem gi in items)
{
total += gi.Amount;
}
return total.ToString();
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
As for the description I'd suggest also grouping by that, and writing another converter to pull out the description from the Items in a similar manner to above.

Categories