Hide ListBoxItems not working - wpf

I have a Textbox and a ListBox
<TextBox FontSize="12pt" Text="{Binding NameFilter, UpdateSourceTrigger=PropertyChanged}" />
<ListBox x:Name="EmployeeList" ItemsSource="{Binding EmployeeList}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The Property of my ViewModel
public string NameFilter
{
get { return _nameFilter; }
set {
_nameFilter = value;
FilterEmployees();
}
}
FilterEmployees set the Property IsVisible of each Employee to true or false.
I have two Problems:
1.)
My Setter gets called after each user input and my EmployeeList gets filtered.
this.RaisePropertyChanged(() => this.EmployeeList);
Gets called. But I see no updates to my list.
2.)
When I manually Update my List (through another function) the Items dissapear. But they never reapear. My filter set everything to IsVisible=true, when the input string is empty (checked it inside the debugger). But not UI update happens.
What am I missing?! I used this answer, but that seems not to be my problem, since it works if I trigger another Action inside my program that updates the list:
WPF - hiding listbox items
EDIT:
I am sorting my Items (actually moving them inside the ObsservableCollection). This way the changes get visible (Employees are hidden). But this just works with hiding, they never reaper.
It seems like the whole stuff gets triggered to late. I want only employees with a "m" inside their name. I have to manually refresh the List two times. Sometimes there are still some Users left, who don't have a "m" inside their name.

You can filter the bound collection of List through CollectionView. Your implementation can be as follows : Get CollectionView for ListBox's ItemsSource and define the filter delegate. Using CollectionView and Filter you are not updating the actual ItemSource but putting a filter for what to show based on filter predicate.
private void FilterEmployees()
{
ICollectionView items = CollectionViewSource.GetDefaultView(EmployeeList);
if (items != null)
{
items.Filter = SearchFilter;
}
}
You can store as the CollectionView member variable so that you don't need to get the CollectionView again and again
public bool SearchFilter(object filterObject)
{
var filter = filterObject as <<List Box item type>>;
if (filter == null)
{
return false;
}
<<Your search logic here.......>>
}
Also, the type of EmployeeList should be ObservableCollection which I found in your edit that you did that.
Now, question here about --- do you have to search on each key stroke or you want to delay the search so that it allows user to type. So to implement this you can create a attach/dependency property to define a delay so that search function will be invoked after defined duration. You have follow the approach mentioned in this link.

Related

WPF DataGrid with continuous selection from the bottom up

How can I make a DataGrid that only supports selection in a continuous range from the bottom and up? The user clicks one item and only this and items below it should become selected. I have a ViewModel with a IsSelected-property that I bind to the RowStyle like this:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource {x:Type DataGridRow}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</DataGrid.RowStyle>
However I try this I get odd and strange behaviours that seem to conflict with how selection is handled by WPF in DataGrids internally. I can't seem to get this work like I want. Any suggestions?
The way I do the selection logic now is that I have a "selection manager" that kicks in when the bound IsSelected-property is updated by WPF and then selects more than one item, specifically all items below and including. But when for instance the user clicks an allready selected item I don't get any events at all (sometimes), perhaps because WPF thinks this is not needed since the property is allready selected. And there is more problems. Trying to think how I can circumnavigate all this internal logic without messing it up to much. I still want the normal selection look on the rows.
I am not sure how your viewmodels are structured, but I will do it this way:
Create a SelectedItems property, as well as an UpdateSelectionCommand in your viewmodel. The GridView.SelectemItems is bound to the VM.SelectedItems
private IList<SelectedItemViewModel> _SelectedItems;
public IList<SelectedItemViewModel> SelectedItems
{
get
{
if (_SelectedItems == null)
{
//populate selected items;
}
return _SelectedItems;
}
private set
{
//set _selected items to null here.
//FirePropertyChanged(): forces repopulation of _SelectedItems based on the new selection
}
}
public ICommand UpdateSelectionCommand
{
get
{
return new RelayCommand(_ => SelectedItems = null, true));
}
}
The UpdateSelectionCommand is triggered by left mouse click (MouseUp event) - this should solve the issue where an item is already selected and re-selecting it does not trigger change.

Binding Visibility to Text.Length

I implemented a small visual indicator (just a textblock with a border), that should be hidden if there is no text to be shown at the moment. The text is bound to the Indicator property, the data context seems to be set correctly.
What I got so far is this (indicator text appears, hide/show doesn't work):
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Indicator.Length}" Value="0">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Indicator}" />
</Border>
My problem is that the element is not hidden if the text length is zero.
Do you spot my mistake?
Indicator is part of the corresponding viewmodel:
public string Indicator
{ get; set;}
UPDATE
It works if I change the property above to this:
public const string IndicatorPropertyName = "Indicator";
private string _indicator = "";
public string Indicator
{
get
{ return _indicator;}
set
{
if (_indicator == value) { return;}
RaisePropertyChanged(IndicatorPropertyName);
}
}
Why does it only work, if I raise PropertyChanged event?
I think as the name implies a Trigger only executes or checks its state when an event occurs.
In case of a DataTrigger it is the PropertyChanged-Event of the interface INotifyPropertyChanged.
Wihout raising the Event the DataTrigger doesn´t know that he has to check the binding and if the value meets the trigger condition.
Your source binding is Indicator which is a plain CLR property.
WPF is based heavily on data binding. Bound objects update automatically when the binding source changes because under the hood a change notification is raised. When bounding to a dependency property (that is a property type understood only by WPF) you already have built in change notification for bound objects. No extra work is required.
You can also bound to plain CLR properties but in this case there is no built-in change notification for bound objects. You need to implement the change notification programmatically. This is done by raising the PropertyChanged event. The objects bound to that CLR property will be notified of value change and will update themselves.
In short if you want a CLR property to update a target binding, you need to implement INotifyPropertyChanged and raise the PropertyChanged event.

How to refresh treeview when ItemsSource changes?

My TreeView is bound to an ObservableCollection and uses HierarchicalDataTemplates.
I get the models from a web service.
Only when users click a node in tree, a web service call will be sent to get its child items.
My App has a TabControl, TreeView is on one tabpage, the other tabpage has a datagrid - it has some data selected from treeview. When an item in datagrid is right clicked, I want to locate the item on treeview. But now the issue is when I iterate the treeview,
say, I have an item called
A.A1.A11
and my TreeView has 3 items in the first level:
A
B
C
when I locate A.A1.A11, I want to expand A, A1, and highlight A11.
When I iterate the treeview, first I find A, it matches first path of A.A1.A11,
so I send a web service request to get A's children.
Once I get that, DataContext of the TreeViewItem of A is updated, but the TreeViewItem itself is not.
So when I check A.Items, it is empty and iteratation is unable to continue.
How can I refresh the TreeView & TreeViewItem when its ItemsSource changes?
Here is the xaml for treeview definition
<TreeView x:Name="TreeRoot"
ItemsSource="{Binding RootItems}"
TreeViewItem.Expanded="TreeViewExpanded"
TreeView.SelectedItemChanged="TreeRootSelectedItemChanged"
ContextMenu="{StaticResource DataGridContextMenu}"
PreviewKeyDown="TreeViewKeyDown"
PreviewMouseLeftButtonDown="TreeViewPreviewLeftMouseButtonDown"
PreviewMouseMove="TreeViewPreviewMouseMove" >
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type m:TreeModelBase}"
ItemsSource="{Binding Children}">
<StackPanel>
<Image Source="{Binding ImageFilePath}" />
<TextBlock Text="{Binding Name}" />
<!-- other items removed for brevity -->
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="Background" Value="DodgerBlue"/>
<Setter Property="TextBlock.Foreground"
Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}"
Value="False">
<Setter Property="Background" Value="White"/>
<Setter Property="TextBlock.Foreground"
Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Does RootItems implement INotifyProperty changed and are you calling NotifyPropertyChanged? Collections need to be an ObservableCollection (not a List) for the UI to know about updates.
When populating TreeViewItem's children, you don't update DataContext, you update ItemsSource. So after the service gets the children for A, you need to assign the resulting List (or ObservableCollection) to A.ItemsSource.
In case you're using DataContext and proper data binding technique, update of the TreeViewItem can be achieved by connecting PropertyChanged to each item and deriving TreeViewItem like mentioned below (resetting DataContext and template of the tree item).
This example refreshes only 2nd level items (second condition), might be easily extended of course.
In my case ElementItem is the base class for every item in ObservableList and implements INotifyProperty.
...
itemInObservableList.PropertyChanged += Ep_PropertyChanged;
...
private void Ep_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var twi = this.pageList.ItemContainerGenerator.ContainerFromItem(
(sender as ElementItem).Parent ) as TreeViewItem;
if (twi != null)
{
var twii = twi.ItemContainerGenerator.ContainerFromItem(sender) as TreeViewItem;
if (twii != null)
{
//second level items refresh
var dc = twii.DataContext;
var tpl = twii.Template;
twii.DataContext = null;
twii.Template = null;
twii.DataContext = sender ;
twii.Template = tpl;
twii.UpdateLayout();
}
}
}
I found & managed to get it work. but it is really not optimal at all. What I did, iterate viewModel first, send web service request to get A's children, then send another request to get A1's children. This is the first pass. Second, search treeview from root and generate ItemContainers while iteration. First, search root of tree, get node A (treeviewItem), then apply template, get container from ItemContainerGenerator (see detail # How to refresh treeview when ItemsSource changes?), then I am able to find the item on treeview and expand its path. Anyone knows a better way, let me know. thanks

The MVVM way of presenting controls dynamically

I have a need to display some kind animation as different processes gets started. My initial idea was to simply add some <ContentControl> tags to the XAML and bind them to a property in the View Model object which then simply assigned this property a ProgressBar, some busy spinner or whatever.
This works but I don't like it. The primary reason I don't like it is because the View Model should not involve itself in presentation matters and this pattern clearly breaks that paradigm.
This is pretty much what my (ugly) code looks like atm:
XAML:
<ContentControl Content="{Binding ProcessAAnimation}" />
In View Model class:
public object ProcessAAnimation
{
get { return _processAAnimation; }
private set
{
_processAAnimation = value;
OnPropertyChanged("ProcessAAnimation");
}
}
public object IsProcessARunning
{
get { return _processARunning; }
private set
{
if (value == _processARunning)
return;
_processRunnings = value;
if (value)
ProcessAAnimation = SomeNiftyAnimationControl();
else
{
if (ProcessAAnimation is IDisposable)
((IDisposable)ProcessAAnimation).Dispose();
ProcessAAnimation = null;
}
}
}
// (clipped: More properties for "Process B", "Process C" and so on)
So, is there a better pattern to achieve this. Preferrably, a pattern where I can create my animation controls dynamically using XAML alone?
Please note that I have already tested a solution where I declare three different animation controls and then bind their Visibility property to the View Model state. That, however, is below par in my book because I don't want to just hide the controls, I want them to be gone unless needed. Besides, that would also make it impossible to dynamically use different types of animations for whatever needs may be.
Anyone?
Well your ViewModel knows about the operation and the progress itself. The rest can be accomplished via Triggers. At least thats the way we do it. So your ViewModel has a property "IsLoadingImage" for example, which is set when your viewmodel starts a BackgroundWorker for loading a big image, it also returns the progress reported by the BackgroundWorker "ImageLoadingProgress" now these two properties are enough to pass to your View. Your view, consists of a Progress bar or a custom control for your special animation. You could now bind the "IsLoadingImage" in a Trigger to toggle the ProgressBar/Animation control visibility and the Value of these is bound to "ImageLoadingProgress".
Like i said, thats how we handle it, and our application makes heavy use of MVVM.
Edit respond to a comment: How to change the template in a trigger
<ControlTemplate x:Name="ActiveTemplate" TargetType="{x:Type MyType}">
<!-- Template when active -->
</ControlTemplate>
<ControlTemplate x:Name="DeactivatedTemplate" TargetType="{x:Type MyType}">
<!-- Template when deactivated -->
</ControlTemplate>
<Style TargetType="{x:Type MyType}">
<Setter Property="Template" Value="{StaticResource DeactivatedTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Template" Value="{StaticResource ActiveTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
This assumes that MyType is a control that can has a ControlTemplate and that the DataContext has a Property IsActive to toggle the Template.

WPF: Once I set a property in code, it ignores XAML binding forever more... how do I prevent that?

I have a button that has a datatrigger that is used to disable the button if a certain property is not set to true:
<Button Name="ExtendButton" Click="ExtendButton_Click" Margin="0,0,0,8">
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
That's some very simple binding, and it works perfectly. I can set "IsConnected" true and false and true and false and true and false, and I love to see my button just auto-magically become disabled, then enabled, etc. etc.
However, in my Button_Click event... I want to:
Disable the button (by using ExtendButton.IsEnabled = false;)
Run some asynchronous code (that hits a server... takes about 1 second).
Re-enable the button (by using ExtendButton.IsEnabled = true;)
The problem is, the very instant that I manually set IsEnabled to either true or false... my XAML binding will never fire again. This makes me very sad :(
I wish that IsEnabled was tri-state... and that true meant true, false meant false and null meant inherit. But that is not the case, so what do I do?
Benny's answer provides one approach (and an excellent one). Another is to extend your model to support the "in async operation" state and to add another trigger to respond to that:
XAML:
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding IsInAsyncOperation}" Value="True">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
Code-behind:
private void ExtendButton_Click(...)
{
IsInAsyncOperation = true;
// begin async operation
}
private void OnAsyncOperationComplete(...)
{
// retrieve results etc.
IsInAsyncOperation = false;
}
Note: You'd define the IsInAsyncOperation property on the same class as IsConnected; if that's a view model rather than the Window class, then you'll need to tweak the code-behind accordingly.
There's a much better way to get this functionality in WPF. It's the commanding system, and it's awesome. Any button, menu item, hot key, etc can be linked to a single command which automatically handles enabling/disabling (as you desire in your program). It's also clean and reusable, and sooooo easy to use.
For example, in an application of mine, I have an "about dialog" that shows up when the user hits F1. I created a command called AboutCommand by implementing ICommand.
public class AboutCommand:System.Windows.Input.ICommand
{
public bool CanExecute(object parameter)
{
return true; // in this case the command is never disabled
}
public event EventHandler CanExecuteChanged
{
// not needed in this case, but in commands when CanExecute actually
// changes, this performs the "magic" of disabling/enabling your controls
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
new AboutWindow().ShowDialog(); //happens when the command is executed
}
}
Then, in my window's xaml, I added*:
<Window.Resources>
<local:AboutCommand x:Key="About"/>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Command="{StaticResource About}" Gesture="F1"/>
</Window.InputBindings>
You could also set the command to a button like so.
<Button Command="{StaticResource About}" Content="About this program"/>
Both the F1 key and the button would be disabled if AboutCommand.CanExecute() returned false.
*(I actually did it differently, because I'm using the MVVM pattern, but this works if you aren't using that pattern.)

Resources