Hide ListViewItem in WPF ListView - wpf

How can I hide a ListViewItem in a bound ListView? Note: I do not want to remove it.

Yeah, this is easy.
The first thing you need to do is to add a property to the class you are binding to. For example, if you are binding to a User class with FirstName and LastName, just add a Boolean IsSupposedToShow property (you can use any property you like, of course). Like this:
class User: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName { get; set; }
public string LastName { get; set; }
private bool m_IsSupposedToShow;
public bool IsSupposedToShow
{
get { return m_IsSupposedToShow; }
set
{
if (m_IsSupposedToShow == value)
return;
m_IsSupposedToShow = value;
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("IsSupposedToShow"));
}
}
}
Then, remember, to hide some item, don't do it in the UI - no no no! Do it in the data. I mean, look for the User record that you want to hide and change that property in it behind the scenes (like in a View Model) - let the UI react. Make the XAML obey the data.
Like this:
<DataTemplate DataType="{x:Type YourType}">
<DataTemplate.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSupposedToShow}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<!-- your UI here -->
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="LastName" />
<Binding Path="FirstName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
When you change IsSupposedToShow to false, then the XAML understands it is supposed to change the visibility of the whole DataTemplate. It's all wired up for you by WPF and presto, it's what you wanted in your question!
Best of luck!

The approaches that I'd follow, from most to least preferable:
In ListView.ItemContainerStyle, use a DataTrigger to set Visibility based on a bound property.
Use a style in the ItemTemplate, or in the DataTemplate for the items if you're getting default templates from the resource dictionary.
Set the ItemsSource for the ListView to a CollectionView, and handle the CollectionView's Filter event in code-behind. See MSDN's discussion of collection views for details.
Maintain a separate ObservableCollection as the ItemsSource for the ListView and add/remove items as appropriate.
Under no circumstances would I use a ValueConverter, because I have a possibly-irrational distaste for them.
I think that using a CollectionView is probably the most correct way of doing this, but they're kind of inelegant because you have to write an event handler to implement filtering.

Use a style with a trigger to set the items visibility to collapsed.

This page gave me the answer I needed: http://www.abhisheksur.com/2010/08/woring-with-icollectionviewsource-in.html (See section "Filtering".)
Wow, so much easier than XAML.
Example:
bool myFilter(object obj)
{
// Param 'obj' comes from your ObservableCollection<T>.
MyClass c = obj as MyClass;
return c.MyFilterTest();
}
// apply it
myListView.Items.Filter = myFilter;
// clear it
myListView.Items.Filter = null;

The approach with ListView.ItemContainerStyle
<ListView ItemsSource="{Binding Path=Messages}" Grid.Column="1" Grid.Row="1" x:Name="Messages"
SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False" >
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center" >
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} => {1}">
<Binding Path="AuthorName" />
<Binding Path="ReceiverName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Jerry Nixon's answer did not work for me completely. I've got to change the xaml a little bit.
Collapsed list view item was using small layout space when I was using DataTemplate.Resources,

<ItemsControl>
<ItemTemplate>
<DataTemplate>
<Image Visibility='{Binding Converter=my:MaybeHideThisElementConverter}' />
</Image>
</DataTemplate>
</ItemTemplate>
</ItemsControl>
What we're doing here is delegating the decision to your implementation of MaybeHideThisElementConverter. This is where you might return Collapsed if the User property of your object is null, or if the Count is an even number, or whatever custom logic your application requires. The converter will be passed each item in your collection, one by one, and you can return either Visibility.Collapsed or Visibility.Visible on a case by case basis.

Related

Binding Property to Two Different Controls [duplicate]

This question already has answers here:
Binding usercontrol property to custom class
(1 answer)
Creating generalized user controls with MVVM Light
(1 answer)
Datacontext conflicts
(1 answer)
Closed 4 years ago.
I have a WPF form with a content control and a custom control. The content control swaps in views based on a radio button selection. Once the user takes an action on the view, I set the nocustomer on the parent viewmodel (the WPF form containing the two controls) to false. When this occurs, the visibility of content control correctly disappears. Unfortunately, the visibility of the custom control remains unchanged (it should have also disappeared). I'm actually perplexed because in my mind they have the exact same implementation and therefore should behave the same.
<ContentControl x:Name="ViewSwap" Content="{Binding SearchingViewModel}"
Visibility="{Binding NoCustomer, Converter={StaticResource
BooleanToVisibilityConverter}, Mode=OneWay}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SearchOptions, Path=IsSelected}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource AddressTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<views:CTACallSubmit x:Name="CallSubmit"
Visibility="{Binding NoCustomer, Converter={StaticResource
BooleanToVisibilityConverter}, Mode=OneWay}"/>
Update:
MainWindow's DataContext
public partial class CTALight : Window
{
public CTALight()
{
InitializeComponent();
this.DataContext = CTALightViewModel.GetInstance();
}
}
MainViewModel
public class CTALightViewModel : ObservableObject
{
public static CTALightViewModel _mainViewModel;
public static CTALightViewModel GetInstance()
{
if (_mainViewModel == null)
_mainViewModel = new CTALightViewModel();
return _mainViewModel;
}
private CTALightViewModel()
{
}
}
CTACallSubmit DataContext
<UserControl.DataContext>
<viewmodel:CTACallSubmitViewModel />
</UserControl.DataContext>
The following creates a new instance of CTACallSubmitViewModel and sets the DataContext of the UserControl to this one.
<UserControl.DataContext>
<viewmodel:CTACallSubmitViewModel />
</UserControl.DataContext>
This means that the binding to the NoCustomer property of the other view model won't work unless you specify a source of the binding:
<views:CTACallSubmit x:Name="CallSubmit"
Visibility="{Binding DataContext.NoCustomer,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}"/>
Setting the DataContext of a UserControl like this is usually a bad idea as it breaks the inheritance of the parent's DataContext.

Bind to ViewModel from ContextMenu in ListBox Item

I have a ViewModel with a command 'OpenCommand', a flag 'IsConextMenuVisible' and an observable list 'Links'.
public ObservableList<string> Links { get; set; }
public bool IsContextMenuVisible { get; set; }
public ICommand OpenCommand { get; set; }
in XAML i want the following to work.
<ListBox ItemsSource="{Binding Links}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.ContextMenu>
<ContextMenu Visibility="{Binding IsContextMenuVisible, Converter={StaticResource BoolToVisibiltyHiddenConverter}}">
<MenuItem Header="Open" Command="{Binding OpenCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
</Textblock.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've already tried some binding expressions for the inner bindings on the ContextMenu, but nothing seems to work. Something like:
Visibility="{Binding Path=DataContext.IsContextMenuVisible,
Converter={StaticResource BoolToVisibilityCollapsedConverter},
RelativeSource={RelativeSource AncestorType=ListBox}}"
This is "problematic" as the kids say because the context menu isn't in the visual tree, so no flavor of RelativeSource is going to work.
You can often bind to properties of PlacementTarget, but in this case you need a visual ancestor of the PlacementTarget, and RelativeSource won't do an ancestor of something else.
In WPF, when there's a gap in the visual tree, the last ditch option is always a BindingProxy. Here's what that class looks like (including the URL of the StackOverflow question I stole it from -- that class has been copied and pasted around many, many questions and answers on this site):
// https://stackoverflow.com/questions/24452264/bindingproxy-binding-to-the-indexed-property
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
And you would use it like this. First create the BindingProxy as a resource, in a location where it can "see" the desired element:
<Window.Resources>
<local:BoolToVisibiltyHiddenConverter x:Key="BoolToVisibiltyHiddenConverter" />
<!-- {Binding} with no path will be the window's datacontext, the main viewmodel. -->
<local:BindingProxy Data="{Binding}" x:Key="MainViewModelBindingProxy" />
</Window.Resources>
And then use it for the Source of the binding. The desired DataContext will be the Data property of the proxy object, so provide paths relative to Data:
<TextBlock.ContextMenu>
<ContextMenu
Visibility="{Binding Data.IsContextMenuVisible,
Converter={StaticResource BoolToVisibiltyHiddenConverter},
Source={StaticResource MainViewModelBindingProxy}}"
>
<MenuItem
Header="Open"
Command="{Binding OpenCommand}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</TextBlock.ContextMenu>
Now you've got another problem: The menu is still popping up. It just doesn't happen to be visible. If the user right clicks, it'll pop up invisibly, and suddenly appear when IsContextMenuVisible changes to true. That's not what you want.
You could omit the converter and just bind directly to ContextMenu.IsEnabled: It'll still pop up, but it'll be grayed out. This is consistent with common Windows UI practice.
You could also have a style trigger so that the TextBlock only has a ContextMenu when you want it to have one. Because that trigger is on the TextBlock, it's in the visual tree we can use a conventional RelativeSource for the binding.
<TextBlock Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger
Binding="{Binding Data.IsContextMenuVisible,
RelativeSource={RelativeSource AncestorType=ListBox}}"
Value="True">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu >
<MenuItem
Header="Open"
Command="{Binding OpenCommand}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

highlight combobox item if it's text starts with the text of the combobox's textbox

I want to highlight(make bold and change its color) all the items whose text starts with the text of the combobox's textbox.
I have tried to google the above question but I am unlucky to get any similar results which would solve my problem.
I think just a hint might be more than enough to solve this problem. Though I am a newbie. If it is possible give me a simple example.
Update :
Here is the code that I tried:
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndCorrespondingEffects}"
IsEditable="True" SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
TextSearch.TextPath="GroupName" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<VirtualizingStackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" Width="250">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="ComboBox_PART_Editable">
<Setter Property="Foreground" Value="Red"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding CorrespondingEffect}" />
</VirtualizingStackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
But I dont know with what should I replace ComboBox_PART_Editable and I don't want the whole text I just want to use Text.StartsWith
I assume that the items in your ComboBox are just plain string values. You will have to change that and create a class to display each item. The reason for this is that you will need some bool 'flag' property that you can bind to a DataTrigger that will highlight your entries according to your requirement. So you could do this:
public class CustomComboBoxItem : INotifyPropertyChanged
{
public string Value { get; set; } // Implement INotifyPropertyChanged correctly...
public bool IsHighlighted { get; set; } // ... here, unlike this example
}
Then you'd need a collection property in your code behind or view model:
public ObservableCollection<CustomComboBoxItem> Items { get; set; }
Again, you must implement the INotifyPropertyChanged interface correctly here. Then you could bind it to the ComboBox.ItemsSource property like this:
<ComboBox ItemsSource="{Binding Items}" ... />
By now, this should look like a normal ComboBox with text entries, so we have to provide a DataTemplate to tell the entries to get highlighted when a condition is met... that's what the IsHighlighted property is for:
<DataTemplate DataType="{x:Type YourXmlNamespacePrefix:CustomComboBoxItem}">
<TextBlock Text="{Binding Value}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
The final piece of the puzzle is to set the IsHighlighted properties according to your requirements. For this, we'll need to bind to the ComboBox.Text property so that we know what that value is in the code. For this, add another property next to the collection property and update the item's IsHighlighted properties inside whenever it changes:
public ObservableCollection<CustomComboBoxItem> Items { get; set; }
public string InputValue
{
get { return inputValue; }
set
{
inputValue = value;
NotifyPropertyChanged("Items");
for (int i = 0; i < Items.Count; i++)
{
Items[i].IsHighlighted = Items[i].StartsWith(inputValue);
}
}
}
...
<ComboBox ItemsSource="{Binding Items}" Text="{Binding InputValue}" ... />
Well that was a bit more thorough than I had intended, but there you go. Let me know how you get on.

DataTrigger, Binding to nested properties via TemplatedParent

According to msdn, it should be perfectly legal, and possible, to bind something to a nested property:
<Binding Path="propertyName.propertyName2" .../>
<Binding Path="propertyName.propertyName2.propertyName3" .../>
In my case, it's not so, though...
I have a custom control, MyControl, with a dependency property ViewModel:
public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
"ViewModel", typeof(IViewModel), typeof(MyControl));
public IViewModel ViewModel
{
get { return (IViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
and in the control template, I try to bind to properties in that viewmodel:
<Style TargetType="{x:Type my:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:MyControl}">
<Grid>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
<Button x:Name="MyButton" Content="Visible by trigger" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.ButtonVisible}" Value="True">
<Setter TargetName="MyButton" Property="Visibility" Value="Visible" />
</DataTrigger>
.../>
In the viewmodel itself, I have a preoperty Text as follow:
public string Text
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("Text");
}
}
public bool ButtonVisible
{
get { return m_buttonVisible; }
set
{
m_buttonVisible = value;
OnPropertyChanged("ButtonVisible"); }
}
I get no bind errors, but things doesn't happend...
Any clues?
Edit
It looks like the bindings work half way. When the text is changed in the editbox, my Text property is set, but if the Text-property is set in code, the ui won't update.
Edit 2
Looks like my first attempt at simplifying the case before posting was a little to successful... As #Erno points out, the code that I posted seems to work OK.
I have looked at the original code some more, and added a trigger to the scenario. The original code uses triggers to show parts of the ui at given conditions. These are also binded to nested properties. I now think that these triggers fail to trigger. I have updated the code. If it still doesn't show whats wrong, I can post a sample application some where.
There is a comma missing:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
EDIT
Add Mode=TwoWay to the binding:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text, Mode=TwoWay}"/>
EDIT2
Got it! I could reproduce and fix it.
Replace the TemplatedParent with Self in the binding.
Read this explanation

Change control in a DataGridRow programmatically

I have a WPF DataGrid contains a list of Products with an image button in the last column to add the Product item into the Orders_Products collection. That works.
Now I'd like to change the "add" image button with a "delete" image button if the Product item is already in the Orders_Products collection.
I tried to use the LoadingRow event but it seems there's no way to access the Image object because is not ready yet in the LoadingRow event...
I tried the Load event of the Image object, but it's not fired if the row is not visible in the form (when I must scroll the datagrid to see that row). It fires when I sort a column and the row is directly visible in the form. I'm going crazy... :(
I think I'm not doing something unusual but I'm new to WPF and probably I miss something.
Can someone give me an hint?
Thanks in advance,
Joe
P.S.: I'm using Entity Framework.
The proper way of doing this is by binding the DataGrid to the collection of objects you want to display (you are probably doing this already).
Then, manually define a DataGridTemplateColumn and set its CellTemplate to an appropriate DataTemplate (this would usually be defined as a resource in your DataGrid or somewhere higher in the logical tree of elements).
You can see an example of how the above is done here.
Inside the DataTemplate, use the technique described in my answer to this question to vary what is displayed in the template by matching the value of an appropriate property in the databound Product.
All of this can be done entirely in XAML, as is the preferred way of doing things in WPF.
Update (working example)
The Product:
public class Product
{
public string Name { get; set; }
public bool Exists { get; set; }
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
this.Products = new List<Product>
{
new Product { Name = "Existing product", Exists = true },
new Product { Name = "This one does not exist", Exists = false },
};
InitializeComponent();
this.DataContext = this;
}
public IEnumerable<Product> Products { get; set; }
}
MainWindow.xaml:
<Window x:Class="SandboxWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ButtonColumnTemplate" >
<ContentControl x:Name="MyContentControl" Content="{Binding}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Exists}" Value="True">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Remove product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Exists}" Value="False">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Add product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Product Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Add/Remove" CellTemplate="{StaticResource ButtonColumnTemplate}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

Resources