I have UserControl containing a procedurally generated ItemsControl. Each item in the ItemsControl contains a ListBox and there is no consistent number of how many items will be generated. The selected item in the listbox is bound to am object (SelectedClass) in the ViewModel. The initial value of the SelectedClass object is null.
The scenario I am running into is this:
User selects ListBoxItemA from ItemsControlItemA, PropertyChanged fires, SelectedClass object is set to the proper value.
User then selects ListBoxItemA from ItemsControlItemB, PropertyChanged fires, SelectedClass object is set to the proper value.
User then selects ListBoxItemA from ItemsControlItemA, but since the selection in that list is still considered to be the same item from step 1, PropertyChanged does not fire, and the SelectedClass object remainsListBoxItemA from ItemsControlItemB.
So my question is, how do i get the UpdateSourceTrigger event to fire OnClick rather than on PropertyChanged, and is that even the best way to approach it? I'm using the MVVM Light framework.
Thanks
<ItemsControl ItemsSource="{Binding AllUpcomingClasses}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding classDescription}" />
<ListBox Name="availableClasses"
ItemsSource="{Binding ClassInstances}"
SelectedItem="{Binding
DataContext.SelectedClass,
Mode=TwoWay}
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type UserControl}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<TextBlock Text="{Binding ClassDate}" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit: Cleaned up the example a bit for readability.
You could handle the PreviewMouseLeftButtonDown event of the ListBoxItem container and "manually" set the SelectedItem property of your view model if the clicked item is the one that is already selected:
<ListBox SelectedItem="{Binding SelectedItem}" xmlns:s="clr-namespace:System;assembly=mscorlib">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListBox.ItemContainerStyle>
<s:String>A</s:String>
<s:String>B</s:String>
<s:String>C</s:String>
</ListBox>
private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
if (lbi != null)
{
YourViewModel vm = DataContext as YourViewModel;
if (vm != null)
{
var selectedItem = lbi.DataContext as YourObjectType;
if (vm.SelectedItem == selectedItem)
{
vm.SelectedItem = selectedItem;
e.Handled = false;
}
}
}
}
If you don't want to handle this in the code-behind of the view you could wrap the same functionality in an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf. The former approach doesn't really break the MVVM pattern though since you are just kind of "extending" the ListBox control functionality to be able to set the same view model source property that the ListBox control sets for you when you select a new item. This functionality belongs to the view or the control.
Related
I have a ListBox in which I am having large list of data. I want to search for items in this list using the SearchBox so that as per the text entered in the SearchBox the list of items in the ListBox should be altered accordingly.
I looked on the internet but could not find any examples of how to do it.
Your help/suggestions will be much appreciated.
here is my XAML
<SearchBox
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Height="50"
Margin="10,0,10,10"/>
</StackPanel>
<ListBox
x:Name="lbSkills"
Grid.Row="1"
Margin="10,0,10,10" SelectionChanged="lbSkills_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="Beige">
<Grid Width="auto" HorizontalAlignment="Stretch">
<TextBlock VerticalAlignment="Center" FontSize="26" Grid.Column="0" Foreground="Black" Text="{Binding SkillDescription}"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You need a textbox, a button, and a listbox or DataGrid.
You connect the textbox to your viewmodel property. Connect the button to your viewmodel command property. Connect the listbox or datagrid source to an observable list property on the viewmodel. Run a linq search query when the button is executed and have it fill the observable list.
If you need code specific solution, send a comment.
Update 1:
Link to a sample project with Search implemented
https://mahowling.wordpress.com/2014/07/31/wpf-search-text-box-for-mvvm/
You can filter the bound collection of List through CollectionView.
Your implementation can be as follows :
Define the TextChanged handler; Get CollectionView for ListBox's ItemsSource and define the filter delegate. You can store as the CollectionView member variable so that you don't need to get the CollectionView again and again
private void txtSearchFilter_TextChanged(object sender, TextChangedEventArgs e)
{
ICollectionView items = CollectionViewSource.GetDefaultView(lbSkills.ItemsSource);
if (items != null)
{
items.Filter = SearchFilter;
}
}
public bool SearchFilter(object filterObject)
{
var filter = filterObject as <<List Box item type>>;
if (filter == null)
{
return false;
}
<<Your search logic here.......>>
}
Since, text Changed event fires on each key stroke so you can implement do delay search. You can follow the this article to implement that.
I have an Entity Framework model and TreeView which has a binding with model.
in constructor:
Context.TestCategory.Load();
my TreeView:
<TreeView x:Name="DbTree" ItemsSource="{Binding Context.TestCategory.Local}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Tests}" >
<TextBlock Text="{Binding Name}" ContextMenuOpening="ContextMenu_ContextMenuOpening">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Добавить тест" Click="TestAdd" CommandParameter="{Binding
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
........................................
other items
........................................
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
After updating items in ItemsSource I need to update the related TreeView branch, what is the best way to do this?
I'm not sure binding your ItemsSource directly to a context property is a great thing to do.
What you're supposed to do with binding is bind to a property that can do RaisePropertyChanged when it has changed, and then the UI elements will update if needed. I use MVVM so my binding is all from a ViewModel which is the datacontext for the View, and which implements the INotifyPropertyChanged interface. Then within that my property setter can look like this:
private List<TestCategories>_myItemSourceList;
public List<TestCategories> MyItemSourceList
{
get { return _myItemSourceList; }
set
{
if (value != _myItemSourceList)
{
_myItemSourceList= value;
RaisePropertyChanged(() => MyItemSourceList);
}
}
}
And the XAML would look like ...
<TreeView x:Name="DbTree" ItemsSource="{Binding MyItemSourceList}">
Obviously because you're not using MVVM your code will be slightly different, but the principle remains: bind your ItemSource to a property that can RaisePropertyChanged.
EDIT: My RaisePropertyChanged is within the Prism framework, so is using a lambda, but the 'standard' was is with a string like this RaisePropertyChanged("MyItemSourceList")
I'm having an application using MVC. It has a canvas and property grid. When an item is selected in the canvas. The property grid should display its details.
So I made an event listener and when item is selected in the canvas it raises an event to the controller which pass the selected item to the property grid to display the details.
Model :
Item object containing name, description
Controller :
protected Controller(object model, FrameworkElement view)
{
this._model = model;
this._view = view;
}
public virtual void Initialize()
{
View.DataContext = Model;
}
View :
<TextBlock>Status</TextBlock>
<ComboBox ItemsSource="?????"/>
Where view is the property grid and model is the selected item.
The problem is in the property grid there is a dropdown list containing lookup values how can I get the dropdown values given that the datacontext of the property grid has already been set to the selected item which doesn't contain reference to these lookup items.
I know that it's easy to use custom code to do that. But I don't want to violate the MVC aproach.
Bind to a source rather than DataContext, sources are provided by ElementName, RelativeSource & Source, so you can name the View for example and use ElementName to get it as source then the Path could be DataContext.LookupValues or whatever your property in the model (- the DataContext of the View is your model -) is called.
e.g.
<Window ...
Name="Window">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ComboBox ItemsSource="{Binding ElementName=Window, Path=DataContext.Occupations}"
SelectedItem="{Binding Occupation}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- ... --->
Edit: Your problem seems to be that you do not pass the information you need, consider a design which still grants you access to more than just the SelectedItem of some list, e.g.
<Window ...
Name="Window">
<ListBox Name="listBox" ItemsSource="{Binding Data}" />
<ContentControl DataContext="{Binding ElementName=listBox, Path=SelectedItem}">
<ComboBox ItemsSource="{Binding ElementName=Window, Path=DataContext.Occupations}"
SelectedItem="{Binding Occupation}" />
</ContentControl>
<!-- ... --->
The DataContext of the ContentControl may be the SelectedItem of the ListBox but the ComboBox inside can still reference the DataContext of the Window which should provide the necessary information.
This is similar to my first example in that the DataContext inside the DataTemplate is always an item of the collection but you can access external DataContexts using sources in your bindings.
I've got an ItemsControl with an ItemsSource of Hours.
I represent each item by a border (in the data template).
Now, each of those border has an hour data, and i want to retrieve that in code behind.
is it even possible?
my code example:
<ItemsControl x:Name="dayHours">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="dayHourBorder" Height="30" BorderBrush="#B0B6BE" Width="193" BorderThickness="1,0,1,1" Background="AliceBlue" Tag="{Binding Index}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And in the naive way, I would expect a code like:
(sender as Border).hourTime;
From your code, I am guessing that you are interested in finding the 'hourTime' in an event handler? When an ItemsControl creates an 'instance' of your DataTemplate for each item, it sets the DataContext of the template to the item itself. Therefore the following should work:
Border border = sender as Border;
MyItemType item = border.DataContext as MyItemType;
var hourTime = item.hourTime;
I've spent far too much time with this and can't find the mistake. Maybe I'm missing something very obvious or I may have just found a bug in the WPF Element Host for Winforms.
I am binding a ListView to a ObeservableList that lives on my ProductListViewModel.
I'm trying to implement searching for the ListView with the general Idea to just change the ObservableList with a new list that is filtered.
Anyway, the ListView Binding code looks like this:
<ListView ItemsSource="{Binding Path=Products}" SelectedItem="{Binding Path=SelectedItem}" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the ViewModel code is as vanilla as it can get:
private ObservableCollection<ProductViewModel> products;
public ObservableCollection<ProductViewModel> Products
{
get { return products; }
private set
{
if (products != value)
{
products = value;
OnPropertyChanged("Products");
}
}
}
Now the problem here: Once I debug into my OnPropertyChanged method, I can see that there are no subscribers to the PropertyChanged event (it's null), so nothing happens on the UI..
I already tried Mode=TwoWay and other Binding modes, it seems I can't get the ListView to subscribe to the ItemsSource...
Can anyone help me with this? I'm just about to forget about the ElemenHost and just do it in Winforms
greetings Daniel
Is there any binding error in the output window?
By the way, you should consider getting the collection view wrapping your products, and then filtering the view, instead of replacing the whole collection.
The code would be something like:
var collectionView = CollectionViewSource.GetDefaultView(Products);
collectionView.Filter += item => ...;