c# / XAML highlight row in listbox that matches parent data - c#

I have the following listbox -- the overall Datacontext goes to the class BatchRef
<ListBox x:Name="Details"
ItemsSource="{Binding BatchRef.ScheduleGroups}">
<ListBox.ItemTemplate>
<DataTemplate>
<localData:ScheduleGroupControl></localData:ScheduleGroupControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
BatchRef has an CurrentID field, and the ScheduleGroup Class has an ID field. I am looking for a way to determine if a ScheduleGroup ID field matches the CurrentID on the BatchRef field. One of the listbox items will always be a match to the parent, and I need to highlight the correct row.

You can use a DataTrigger to do this.
Something like this in your ItemTemplate:
<ListBox ItemsSource="{Binding Current.Groups}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CurrentId,
StringFormat={}CurrentID: {0}}"/>
<TextBlock Text="{Binding Path=DataContext.Current.CurrentId,
StringFormat={}ParentID: {0},
RelativeSource={RelativeSource AncestorType=Grid}}"/>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Background" Value="Red"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IntEqualConverter}">
<Binding Path="CurrentId"/>
<Binding Path="DataContext.Current.CurrentId"
RelativeSource="{RelativeSource AncestorType=Grid}"/>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Background" Value="Green"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've used a IMultiValueConverter to check if the two values are equal:
public class IntEqualConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values.OfType<int>().Distinct().Count() == 1;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
This is a crude example. But it should point you in the right direction.
The important things to note are:
You need to use RelativeSource on the binding otherwise the DataContext will be that of the ListBoxItem, not your view's DataContext.
You will need to add the converter as a resource.
If it helps to understand. The Current property on my ViewModel is a BatchRef Object. The BatchRef class has a property called Groups which of type ObservableCollection<ScheduleGroup>.

Related

How do I collapse an Expander whenever the DataGrid inside is null using XAML?

I have some code like this
<Expander IsExpanded="{Binding HasData}">
<DataGrid ItemsSource="{Binding SomeDataSource}">
<!-- some sode -->
</DataGrid>
</Expander>
Right now I am programmatically setting HasData to true whenever SomeDataSource is set to something not null.
Is it possible to achieve this using just XAML, without any c# code?
You could also use a DataTrigger and save yourself the converter.
<Expander>
<Style TargetType="{x:Type Expander}">
<Style.Triggers>
<DataTrigger Binding="{Binding SomeDataSource, ConverterParameter=SomeData}" Value="{x:Null}">
<Setter Property="IsExpanded" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander>
One possible way is to use a converter to bind to SomeDataSource too:
public class IsNullConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter == null)
return value == null;
else
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Then, you can use it in XAML only:
<Window.Resources>
<l:IsNullConverter x:Key="IsNullConverter"/>
</Window.Resources>
<Expander IsExpanded="{Binding SomeDataSource, Converter={StaticResource IsNullConverter}, ConverterParameter=SomeData}">
<DataGrid ItemsSource="{Binding SomeDataSource}">
<!-- some sode -->
</DataGrid>
</Expander>
EDIT
If it is no problem in your case, you can also bind to the DataGrid.HasItems property. It determines, if there are items in the source list. But it collapses the Expander too, when the source contains no items, but it is not null (source is an empty list).
<Expander IsExpanded="{Binding HasItems, ElementName=dg, Mode=OneWay}">
<DataGrid Name="dg" ItemsSource="{Binding SomeDataSource}">
<!-- some sode -->
</DataGrid>
</Expander>
Using a DataTrigger with an Expander Style and binding to the DataGrid itself works.
<Expander>
<Expander.Style>
<Style TargetType="{x:Type Expander}">
<Setter Property="IsExpanded" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyDataGrid, Path=ItemsSource, UpdateSourceTrigger=PropertyChanged}" Value="{x:Null}">
<Setter Property="IsExpanded" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding SomeDataSource, UpdateSourceTrigger=PropertyChanged}">
<!-- some code -->
</DataGrid>
</Expander>

How to get first ListBoxItem in the group

I have a ListBox sorted and grouped by SortDescription and GroupDescription and now I want to know whether there is a way to know which item is the first one in each group. For example if I have a list of names I want to get the first Item in the list which starts with A, B, C, etc and change its template.
To be more clear I should say I want to change the DataTemplate of the first ListBoxItem in each group. How is it possible?
Edit
This is my sample CollectionView
<CollectionViewSource
x:Key="Myiew"
Source="{Binding Items}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="FirstName" Converter="{StaticResource StringToFirstLetter}" />
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="FirstName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Listbox ItemsSource={"StaticResources Myiew"} />
As I said everything works fine. the Items are sorted and if I change the GroupStyle I see that the items are grouped, but I don't want to set a group style. I just want to change the DataTemplate of the first item in each group.
Here is a sample for you
create a converter class
this will just see if the value passed is null or not
class FirstItemDetector : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
then comes the declaration of converter where l: is the namespace to your converter class
<l:FirstItemDetector x:Key="FirstItemDetactor" />
then comes the listbox
I added a group style (you can style however you like) solution is group independent
added a data template as ItemTemplate of the listbox
added a DataTrigger on PreviousData with converter as FirstItemDetactor declared as above
in the setter of trigger I changed the foreground to red ( you have many choice including changing styles, show hide elements etc.
here is the listbox
<ListBox ItemsSource="{Binding Source={StaticResource Myiew}}">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border BorderBrush="Black"
BorderThickness=".5"
Padding="4">
<TextBlock Text="{Binding Name}"
HorizontalAlignment="Center" />
</Border>
<ItemsPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" x:Name="text" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData},Converter={StaticResource FirstItemDetactor}}"
Value="True">
<Setter TargetName="text"
Property="Foreground"
Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
main role is played by the datatigger and the converter, it will work independently, with groups or even nested groups.

Using ValueConverter in Style DataTrigger of ItemsControl but List is Empty

I am attempting to use a ValueConverter to flip the last item in an ItemsControl (so that it appears backwards).
To accomplish this, I created a Style with a DataTrigger which is using a ValueConverter to check if the current item is the last item in the list.
<UserControl.Resources>
<local:FIsLastItemInContainerConverter x:Key="IsLastItemInContainerConverter"/>
</UserControl.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=ActiveAction.ActionIconDatas}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="local:FActionInfoControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsLastItemInContainerConverter}}" Value="True">
<Setter Property="RenderTransformOrigin" Value="0.5 0.5"/>
<Setter Property="RenderTransform">
<Setter.Value>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
</TransformGroup>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<ContentControl>
<local:FActionInfoControl/>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
The issue appears to be with my ValueConverter.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DependencyObject item = (DependencyObject)value;
// THIS IS RETURNING NULL
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
if (ic == null)
{
return false;
}
else
{
return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
}
}
Although item is a valid element in the ItemsControl, the call to ItemsControl.ItemsControlFromItemContainer is returning null and I'm not sure why. It is being set, and displays fine (it just is never flipped as the style should cause it to).
Any ideas on this? Thanks!
Try replacing your binding with:
{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}, AncestorLevel=2}, Converter={StaticResource IsLastItemInContainerConverter}}
Actually ItemsControl.ItemsControlFromItemContainer() only returns parent ItemsControl for actual item containers. In your case ContentPresenter is the container type as you use simple ItemsControl. However as you incapsulate your in a ContentControl in your data template it is important to specify AncestorLevel=2 as well.
You are passing local:FActionInfoControl to converter, which is not a container for ItemsControl. You need to pass container i.e. ContentPresenter which you can get using FindAncestor.
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ContentPresenter, AncestorLevel=2},
Converter={StaticResource IsLastItemInContainerConverter}}"
Value="True">
.....
</DataTrigger>

Bind checkbox visibility to converted bool and another checkboxes visibility

Below binds the visibility of my checkbox to the converted bool. That works fine. How would i add a second condition? I only want to make the checkbox visible if the converted bool is true and another checkbox called Allowed is checked.
<CheckBox Grid.Row="3" Foreground="Black" Grid.ColumnSpan="2" x:Name="IsItComplete" IsThreeState="False"
BorderBrush="Black" VerticalContentAlignment="Center"
Checked="IsItComplete_Checked"
Style="{StaticResource CheckBoxStyle1}"
Visibility="{Binding Job.CanItBeComplete, Converter={StaticResource booleanToVisibilityConvertor},
Mode=OneWay,
Source={StaticResource Locator}}">
<CheckBox.Content>
<TextBlock Text="Is It Complete" Margin="0"/>
</CheckBox.Content>
</CheckBox>
If you are working under MVVM, one approach is to create a property in the ViewModel which will handle the logic and return a boolean indicating if the checkbox should be visible or not.
The second check box will have to be bound to a property as well, and be sure to do a TwoWay binding so that the property is updated when the checkbox is checked or not.
This should help: Bind two elements' Visibility to one property
This can be solved via two approaches i can think of over my head (ofcourse having single property in View model already suggested so i am not going to talk about it again)
Approach 1
Bind Visibility of checkBox with MultiValueConverter in place. Pass two bindings to it:
Actual bool property of class.
IsChecked DP of Allowed checkbox.
Converter will do AND operation on two values and will return accordingly.
<CheckBox>
<CheckBox.Visibility>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Path="IsEnable"/>
<Binding ElementName="Allowed" Path="IsChecked"/>
</MultiBinding>
</CheckBox.Visibility>
</CheckBox>
MutliValueConverter code:
public class MyConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
return ((bool)values[0] && (bool)values[1])
? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
Approach 2
Use MultiDataTrigger in checkBox style like this:
<CheckBox>
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsEnable}" Value="True"/>
<Condition Binding="{Binding ElementName=Allowed,
Path=IsChecked}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>

How to make a TextBlock appear / disappear based on bound property of another TextBlock?

Supposing that I have two TextBlock elements, one being a label for the second, which is bound:
<TextBlock Margin="0,0,0,0" Text="Notes:" />
<TextBlock Margin="50,0,0,0" Text="{Binding Path=notes}" />
I only want these two TextBoxes to appear if notes!="", that is only if there is something to display. How would one go about this?
Thanks.
so many ways to do it, DataTriggers, doing logic in your ViewModel, DependencyProp's in code behind so you can control everything through binding without any triggers, etc.
or here's a sample doing in XAML only.
Copy/Paste/Run this code:
<Control>
<Control.Style>
<Style TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<StackPanel x:Name="stackPanel">
<TextBlock Margin="0,0,0,0" Text="Notes:" />
<TextBlock x:Name="txtNotes" Margin="50,0,0,0" Text="{Binding Path=notes}" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger SourceName="txtNotes" Property="TextBlock.Text" Value="">
<Setter TargetName="stackPanel" Property="Control.Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Control.Style>
</Control>
First create a converter:
public class EmptyStringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return string.IsNullOrEmpty(value as string)
? Visibility.Collapsed
: Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Then reference it (you can do this in your App resources, in the view resources, etc:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converters="clr-namespace:MyConverterNamespace">
<Converters:EmptyStringToVisibilityConverter
x:Key="EmptyStringToVisibilityConverter"/>
</ResourceDictionary>
Then use it in your controls:
<TextBlock Margin="0,0,0,0" Text="Notes:"
Visibility="{Binding notes,
Converter={StaticResource EmptyStringToVisibilityConverter}"/>
<TextBlock Margin="50,0,0,0" Text="{Binding Path=notes}"
Visibility="{Binding notes,
Converter={StaticResource EmptyStringToVisibilityConverter}"/>

Categories