WPF Listview empty text - c#

How to show in the WPF Listview using the GridView an empty text (like in ASP.net), e.g. "please select a person" or "0 items founded"?

This XAML will do something similar, it has a visible ListView showing a list and a hidden message and switches visibility when the list is empty using a trigger.
The code below will work with any IList or ICollection but the same technique can be used with any data source, like always, if you want the display to update when you add or remove items you need to use an ObservableCollection or similar.
The ContentPresenter is there because you can only use triggers inside a template or a style, so we put our controls inside a DataTemplate and use the ContentPresenter to show it.
If you want the message to appear inside the ListView than all you have to do is remove the Setter that hides the ListView and add some margin to the TextBlock to position it where the first item in the ListVIew should be.
<ContentPresenter Content="{Binding}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Grid>
<ListView Name="list" ItemsSource="{Binding MyList}"/>
<TextBlock Name="empty" Text="No items found" Visibility="Collapsed"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding MyList.Count}" Value="0">
<Setter TargetName="list" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="empty" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>

Bind it to a DataSource + Property that returns the text you want ?
Slot in a dummy object whose String representation is the text you want..

Related

How do I use a Trigger to synchronize ListBox selections?

I have two list boxes which are bound to same observable collection. Basically one list box is a document tray where I drag & drop documents and other is a regular list box where I give the option to edit the file name. I want to highlight the items in both the listboxes. For example when the user selects a document in one listbox (tray) I want the list item with textbox in the other to be highlighted and likewise when I click on the text box in the other list box I want the item in the tray to be selected. I have my code as below.
In the Document Tray list box I have code as below.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</Style>
</ListBox.ItemContainerStyle>
Similarly in the other list box I have the style like this
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
When I select an item in the document tray the item is getting selected in the other list box. When I click on the text box or select an item in other list box the item is not getting selected in the tray list box. Also I noticed if I comment the focus trigger the items are getting selected appropriately. I want the selection to be propagated on focus also.
I am assuming you are binding your ListBox to data in code-behind (view model or otherwise) via a property, e.g. YourItems:
<ListBox ItemsSource="{Binding YourItems}">
<ListBox.ItemContainerStyle>
...
</ListBox.ItemContainerStyle>
</ListBox>
Create another property of the type that's within the collection in your code-behind, also (e.g. called YourSelectedItem. Then just add the following line inside BOTH of your Listbox definitions:
<ListBox ItemsSource="{Binding YourItems}"
SelectedItem="{Binding YourSelectedItem, Mode=TwoWay}">
As long as both are bound two-way, then they should stay in-sync with each other.
I would not use setters in the style to set IsSelected (discard the lines <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />). The ListBoxes themselves have properties to handle that if you can bind to a common item in code-behind as shown above. I'd need to try it, but then hopefully your trigger would work also since you won't have competing IsSelected triggers/styling.
I think the key thing here is that I've found that sometimes you cannot set something via trigger IF you've also set the same property via a style, so use SelectedItem on the ListBox parents, instead of an IsSelected style on each item.

Updating Values in the Control Template of the ComboBox

I implemented a column in a data grid that containes comboboxes. In order to display a text box in stead of a combobox when a list containes only one value, I used the solution from this post:
How to hide combobox toggle button if there is only one item?
However, when that one value in the list is changed, it is not updated in the text box. I have, of course, implemented INotifyPropertyChanged and it works as long as I have more than one item in the list (in other words, when the combobox is shown) but the value in the TextBlock is never updated.
Edit:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="CList" ItemsSource="{Binding Values, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="0" BorderBrush="Transparent"
Background="Transparent">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count, ElementName=CList}" Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding Items[0], ElementName=CList}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
I can see you are binding to the item itself, instead of any property in it
so perhaps you many need to bind to the respective property of your data item
eg
<TextBlock Text="{Binding Items[0].MyProperty, ElementName=CList}" />
assuming your intended property is MyProperty
Note if there is no underlying property then you'll have to remove the item and add new one again to list in order to update the text block, in this scenario INotifyPropertyChanged will also not work

How can I add a template for datagrid's NewItemPlaceholder row?

I have implemented a datagrid in my WPF application using the MVVM design pattern.
Each row of the datagrid has a combobox and a another control based on the selection of the combobox and everything works fine.
The problem is that the NewItemPlaceholder (the row that enables me to add new objects to my ObservableCollection) by default displays DataGrid.NewItemPlaceholder. As I have read here the problem is that I must create a datatemplate for that row. And in that link it is described how to do it programatically. Is there a way to do it directly in XAML?
Thank you in advance.
Try this out:
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<buttons:CloseButton x:Name="CloseButton"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}},
Path=IsNewItem}" Value="True">
<Setter TargetName="CloseButton" Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
I use a trigger to hide the button, only on the New Item row. With a little ingenuity I ought to be able to swap out entire templates, like use a content presenter and change its template or something similar.

WPF Listview & Checkboxes - Binding issue when using relative source

I have a listview with two columns, one contains a textbox and the other a checkbox. These are bound to an ObservableCollection of a custom object containing a string for the textbox and a boolean for the checkbox.
All was working well until I tried having the check event of the checkbox highlight it's the row in the listview as in this article.
My problem is that checkbox no longer binds to the ObservableCollection. The textbox binds okay, but changing the checbox declaration from:
<CheckBox IsChecked="{Binding RestrictedEdit}"/>
to this:
<CheckBox IsChecked="{Binding RestrictedEdit, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}"/>
stops the checkbox binding and the listview is displayed with the checkboxes all unchecked irrespectivate of status of the boolean. What am I doing wrong?
You are trying to bind to RestrictedEdit property, which ListViewItem doesn't have. This property is declared in view model, which is stored in DataContext, so this should work:
<CheckBox IsChecked="{Binding DataContext.RestrictedEdit,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}}}"/>
However, I don't see any reason to use this code instead of simple IsChecked="{Binding RestrictedEdit}". CheckBox inherits DataContext from ListViewItem, so there is no reason to use relative source.
Let the binding as it is (no RelativeSource) and use rather a style or a DataTemplate having your custom object class as TargetType, and with a DataTrigger set on RestrictedEdit.
example with style :
<Style x:Key="MyStyle" TargetType="MyClass">
<Setter Property="BackGround" Value="White" />
<Style.Trigger>
<DataTrigger Binding="{Binding RestrictedEdit}" Value="False">
<Setter Property="BackGround" Value="Gray" />
</DataTrigger>
</Style.Trigger>
</Style>
Define this style, say, in your application resources (in App.xaml).
Then in your listview, use :
ItemContainerStyle="{StaticResource MyStyle}"

Change Binding of ContentControl Based on Selection in one of two ListBoxes

I have two ListBox controls in a window. Each is a list of patient allergies bound to different StaticResources. At the bottom of the window I have a ContentControl which displays additional information about the selected allergy in the list above.
Currently, I have two ContentControls, one for each of the listboxes, both with Visiblity="Collapsed". When the user makes a selection, I make the associated content control visible and collapse the other one. I'd like to only have one content control and change its binding.
So far, I've tried each of the following with no luck:
this.ExpandedAllergyDetails.Content = "InsideAllergiesSource";
this.ExpandedAllergyDetails.SetBinding(ContentControl.ContentProperty, "InsideAllergiesSource");
this.ExpandedAllergyDetails.SetBinding(ContentControl.ContentProperty, new Binding());
this.ExpandedAllergyDetails.SetResourceReference(ContentControl.ContentProperty, this.Resources["InsideAllergiesSource"]);
this.ExpandedAllergyDetails.SetResourceReference(ContentControl.ContentProperty, this.FindResource("InsideAllergiesSource"));
In each case, I was trying to use only one ContentControl, named ExpandedAllergyDetails, and change its binding to "InsideAllergiesSource" which is a CollectionViewSource defined in the XAML.
The SetResourceReference takes a key, not the actual object. So you'd use
this.ExpandedAllergyDetails.SetResourceReference(ContentControl.ContentProperty, "InsideAllergiesSource");
If "InsideAllergiesSource" is the x:Key of the resource. I'm not sure this will bind to the "current" item though.
You can wrap those two lists in a ListBox as items, so that when one allergy is selected the corresponding main list is selected as well, that way you can bind to a path of selections, i.e. SelectedMainList -> SelectedAllergy. Not sure how this translates into your specific application code but here is a working example of the scenario, note the Style that is necessary to auto-select the parent list. The example consists of two lists and a TextBlock which displays the chosen item.
<TextBlock Text="{Binding ElementName=TestLB,
Path=SelectedItem.Content.SelectedItem.Content}"/>
<ListBox Name="TestLB">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem>
<ListBox>
<ListBoxItem>List1-Item1</ListBoxItem>
<ListBoxItem>List1-Item2</ListBoxItem>
<ListBoxItem>List1-Item3</ListBoxItem>
</ListBox>
</ListBoxItem>
<ListBoxItem>
<ListBox>
<ListBoxItem>List2-Item1</ListBoxItem>
<ListBoxItem>List2-Item2</ListBoxItem>
<ListBoxItem>List2-Item3</ListBoxItem>
</ListBox>
</ListBoxItem>
</ListBox>
You might want to mask the selection of the two main lists, this answer should help with that.
Edit: Since the trigger in the above style will also unselect the main list when the keyboard focus is moved away from inside the list you might want to change it to only act when the trigger is fired, this can be done with a single frame animation in the EnterActions of the trigger.
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame KeyTime="0:0:0.0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>

Categories