Search items from a List box using Search box - c#

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.

Related

Change field of a TextBox with Button click at the same index in Data Binded List View

I have a ListView with a collection as ItemsSource.
<ListView x:Name="lvBT" Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}"
ItemsSource="{x:Bind ViewModel.CurrentPoste.TableauxBT}" Margin="0,0,0,12"
IsEnabled="{x:Bind ViewModel.CurrentPoste.BtEdition, Mode=TwoWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:BT">
[...]
<TextBox x:Name="tbNumSerieBT" HorizontalAlignment="Stretch" Margin="12,32,16,0" Text="{x:Bind NumSerie, Mode=TwoWay}" VerticalAlignment="Top" Grid.Column="3" FontSize="16" Grid.ColumnSpan="2"/>
<Button x:Name="bScannerBT" Grid.Column="5" HorizontalAlignment="Stretch" Margin="12,32,15,0" VerticalAlignment="Top" Content="Scanner tabeau BT" FontSize="14" Click="BScannerBT_Click"/>
[...]
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
tableauxBT is representing a collection of BT objects and for each object in that collection I'm creating a DataTemplate, like basic data binding.
When I click on the Button in that template I'm scanning a barcode with a BarcodeScanner and want to put the return value in the TextBox field.
For each button of each different BT item I want to scan a different barcode but the problem is that I don't know how to get the index of the Button that was clicked to put the value in the right TextBox.
So how can I do to get the index of the clicked Button to put the value in the TextBox at the same index ?
You can get the index of the clicked button using the "BScannerBT_Click" method.
public BScannerBT_Click(object sender, EventArgs e)
{
var myClickedButton = (Button)sender; //this object hold all information you need.
//You can reach the button's dataContext and change the value you want to.
var buttonDataContext = myClickedButton.DataContext.
//now you have the ViewModel (buttonDataContext) associated with the 'NumSerie'
}
I hope it helps you.

Special Sorting ListView when clicking header

I got a row like this:
XAML:
<ListView x:Name="ListViewAnlagen"
Grid.RowSpan="2"
ItemContainerStyle="{StaticResource TempContainerStyle}"
VerticalAlignment="Top" HorizontalAlignment="Left"
Height="571" Width="1314"
Margin="0,53,0,0"
AlternationCount="2"
GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{DynamicResource CustomHeaderStyle}">
<GridView.Columns>
<GridViewColumn Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border x:Name="border"
BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
<TextBlock Text="{Binding EqNr}" Margin="6,3"/>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="EQ Nr."/>
<Image Source="img/filter.png"
Width="20" Height="20" Margin="25 0 0 0"
MouseDown="Image_MouseDown_1" />
</StackPanel>
</GridViewColumn>
I have added a click handler: GridViewColumnHeader.Click
My Question is, how to sort this ascending and descending. I already looked at some others solutions, but it seems they only work when you bind it with DisplayMemberBinding.
What I already tried:
this
Since you already examined the example as commented by #AmolBavannavar (https://code.msdn.microsoft.com/windowsdesktop/Sorting-a-WPF-ListView-by-209a7d45), here is a hybris between the example and your current approach.
The main obstacle in adapting the example is the usage of GridViewColumnHeader.Command and GridViewColumnHeader.CommandParameter. Your equivalent for the command is the GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler", but you still need an equivalent to the command parameter.
I suggest you create an attached string property for this purpose and use it to attach the sort property name to the GridViewColumn. For the sake of demonstration, I don't create a new property but instead misuse the TextSearch.TextPath attached property:
<GridViewColumn Width="100" TextSearch.TextPath="EqNr">
Note that the "EqNr" is the same as the property name that is used for binding inside the cell template later.
Now, everything is in place to be used inside the click handler.
Get the clicked column header
Get the associated column
Get the attached property value that contains the sort property name
Get the collection view that is associated with the items source (or items)
Change the sort descriptions of the collection view
Code with simplified sorting logic:
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
var h = e.OriginalSource as GridViewColumnHeader;
if (h != null)
{
var propertyName = h.Column.GetValue(TextSearch.TextPathProperty) as string;
var cvs = ListViewAnlagen.ItemsSource as ICollectionView ??
CollectionViewSource.GetDefaultView(ListViewAnlagen.ItemsSource) ??
ListViewAnlagen.Items;
if (cvs != null)
{
cvs.SortDescriptions.Clear();
cvs.SortDescriptions.Add(new SortDescription(propertyName, ListSortDirection.Descending));
}
}
}
Note that for the sake of demonstration I only clear the sort descriptions and add a static descending sort description. For your actual application, you may want to keep track (or analyze) the current sorting status for the column and then alternate between ascending and descending sort.

WPF MVVM Light Multiple ListBoxItems bound to same object

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.

Dynamic text boxes binding

I am trying to make a form of sorts where the user will have the option to press a button to dynamically add more text boxes, with each text box to contain the path of a directory to exclude from a search. This is relatively trivial using code-behind, but my problem is doing it in proper MVVM.
The general markup for the structure is:
<ScrollViewer >
<StackPanel>
<DockPanel LastChildFill="False">
<TextBox DockPanel.Dock="Left"/>
<Button DockPanel.Dock="Right"
Content="+"/>
</DockPanel>
</StackPanel>
</ScrollViewer>
Clicking the button will add a new DockPanel with a TextBox and Button to the StackPanel. All buttons but the bottom-most will change to a minus sign. How can I somehow bind to the text of all the text boxes?
As an aside, once/if I get this working, would it be better to make it into its own component?
Quick pseudo-code to get you started:
cs (view model):
// INotifyPropertyChanged if you need, as well as full-properties with notification
public class Item
{
public string Text {get; set;}
}
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
void OnCommandAdd() => Items.Add(new Item());
xaml (view):
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The idea is to use control able to display list (e.g. ItemsControl) and collection of items in view model. Adding new element to view is done by adding item to that collection.
All TextBoxes will have Text bound to corresponding property of item.

hide or remove border of wpf button in C# code

I have a stackpanel named "mystack" in my xaml file and I am adding buttons in it dynamically from the .cs file and want to remove the border of buttons in C# .cs file
what I really want is to populate this stackpanel with the buttons coming from a list of string
thanks in advance
xaml:
<Grid HorizontalAlignment="Left" Height="227" Margin="10,10,0,0" Grid.Row="2"
VerticalAlignment="Top" Width="530">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="mystack" HorizontalAlignment="Left" Grid.Row="2"
VerticalAlignment="Top" Width="520"/>
</ScrollViewer>
</Grid>
.cs:
public List<String> Schools()
{
List<String> l = new List<string>();
l.Add("SST");
l.Add("SBE");
l.Add("SSH");
return l;
}
I agree with HighCore, you generally do not want to manipulate the UI elements in your code.
To remove the Border of the buttons you can set a Button's BorderThickness property to "0" in Xaml or to new Thickness(0) in the code-behind.
i.e.
myButton.BorderThickness = new Thickness(0);
EDIT:
Okay, I noticed your updated question. I would create a property that stores your list of schools and bind to it in a way similar to this:
public List<string> Schools
{
get { return _schools; }
set { _schools = value; }
}
Somewhere you need to set the DataContext of the control to your class containing the Schools property. If you are dynamically updating the list of Schools you'll need to implement INotifyPropertyChanged so the UI knows when to update. And then your Xaml would look something like this:
<ItemsControl ItemsSource="{Binding Schools}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl>
You can't remove button's border like: btn.BorderThicknes=new Thickness(0);
See this: Removing button's border
The fast Fix:
What I had to do to effectively hide the button border - and due to the button control template I believe which utilizes and changes Button border (i.e. even if you remove it it'd draw it on some trigger I believe)...
...was to set BorderBrush="Transparent" as well (I always do BorderThickness as well but I'm guessing it's not needed - only for visual/layout look'n'feel)
i.e. setting thickness alone is not enough.
I'm really not sure that's the bets way to do it, or actually I'm
quite sure there must be something smarter - but I always end up with
that.
The Right Way:
Proper way - and recommended - is to write your own Button template -
based on the Microsoft official one - or base it on it - and do what
you need w/o borders.
For the code behind/C#:
You really don't need that as per your changed question - do what others suggested already
the best way to do this is :
<Style TargetType="Button">
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="0"/>
</Style>
</Style.Resources>
</Style>
what I really want is to populate this stackpanel with the buttons
coming from a list of string
That's called a ListBox:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0"/>
<!-- Whatever other customizations to the button -->
</DataTemplate
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public class ViewModel
{
public ObservableCollection<string> Items {get;set;}
public ViewModel()
{
Items = new ObservablecCollection<string>();
Items.Add("String1");
Items.Add("String2");
Items.Add("String3");
}
}
You need to learn the MVVM pattern.

Categories

Resources