Special Sorting ListView when clicking header - c#

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.

Related

Cannot set SelectedIndex="0" in Xaml to ListView (Windows Store App)

I have 2 ListViews and a TextBlock. The first ListView1 includes letters in Alphabetical order. And the second ListView2 includes the words that start with the selected letter (in ListView1). When I choose a letter from ListView1 and then click on a word loaded in ListView2, I want to get the definition of this word in a TextBlock.
This is my Xaml:
<ListView
Width="510"
x:Name="ListView1"
ItemsSource="{Binding}"
Background="White"
Foreground="Black"
TabIndex="1"
Margin="-7,8,0,0"
IsSwipeEnabled="False"
SelectionChanged="ItemListView_SelectionChanged"
Grid.Row="1"
HorizontalAlignment="Left">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Grid.Row="0"
Text="{Binding glossary_letter}"
Margin="10,0,0,0"
TextWrapping="Wrap"
Foreground="Black"
FontSize="24"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Width="361"
x:Name="ListView2"
Background="White"
Foreground="Black"
Margin="425,8,230,0"
Grid.Row="1"
HorizontalAlignment="Center"
ItemsSource="{Binding}"
SelectionChanged="itemListView2_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Grid.Row="1"
TextWrapping="Wrap"
Foreground="Black"
Text="{Binding}"
FontSize="24"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel HorizontalAlignment="Right"
Background="White"
Width="580"
Margin="0,10,0,0" Grid.Row="1" Grid.ColumnSpan="2">
<TextBlock x:Name="defBlock" Foreground="Black" Text="{Binding glossary_definition}"></TextBlock>
</StackPanel>
If I click the first time on a letter (ListView1) then on a word (ListView2) it shows me the definition. However the second time I click on a letter, it gives me an OutOfRange Error where the ListView2.SelectedIndex = -1
This is my C# code:
private void ListView1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView2.ItemsSource = arrayW[ListView1.SelectedIndex];
}
private void ListView2_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
defBlock.Text = arrayDef[ListView1.SelectedIndex][ListView2.SelectedIndex];
}
Any idea what is the error I am doing?
private void ListView2_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(ListView2.SelectedIndex >= 0){
defBlock.Text = arrayDef[ListView1.SelectedIndex][ListView2.SelectedIndex];
}
else
{
defBlock.Text = arrayDef[ListView1.SelectedIndex][0];//set default selected word..
}
}
The problem
You need to manage your list2 selected index changed handler, as every time you update your list one there is a selected index change on list 2 and as there is no selected index it defaults to -1.
There's a number of ways to do this.
1.
private void ListView2_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(ListView2.SelectedIndex == -1)
// do something or
// eg.
return;
// or
throw new IndexOutOfRangeException("Message");
//or
throw new Exception(); // catch all
}
2.
I'm not sure how you want your app to look like.
I'd be using two separate pages for this. And have the xaml for your first list view and then a second page is viewed and bound to the selected index of your first page.
So list1, you select and that then is easier to set as the data source in a new page which shows list2, and then you can update your textbox with the details from a selected item. or further, create a third page if you wanted to show more extensive details of the word and it's definition.
This way you will not have problems with your List2 having no selected index as the data source is changed.
3.
Or,
Take the binding declarations out of the index changed handler and call them methodically when an index is in List1 is selected.So when the selection of List1 is changed, List 2 is updated in other words, you need to update your data source. edit: and with this it's another way of you controlling the use of error handling to avoid an outofrange exception, as the datasource is updated.
So possibly put the following into a separate method.
private void MyTextMethod(){
defBlock.Text = arrayDef[ListView1.SelectedIndex][ListView2.SelectedIndex];
}
private void ListView2_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try{
MyTextMethod)();
}
catch(OutOfRangeException){
// do something.
}
}
out of your selected index changed handler and make a call to a separate method when from within the handler.
4.
Take your binding declaration for list2 from out of your selectedindex change handler for list1.
So you could have a method that will update the binding source of list2 and manage the selected index changed handler. Though this is the least useful suggestion.
Bottom line: You need to have some try and catch, or throw statement managing the outofrange exception, as the second list will have varying lengths and the index on letter As list may be selected at 10, and then the letter X may only have a list of length 1 and there is always the issue of the selectionchange returning a selection of -1.
( You don't actually need to clear list2, it is cleared automatically as the data source is changed (sorry, I didn't make that clear))

Why does my dropdown feel so clunky?

I have a XAML UserControl embedded in a WinForms/WPF Interop ElementHost control. The control is pretty simple - it's just a dropdown with a button - here's the entire markup:
<UserControl x:Class="Rubberduck.UI.FindSymbol.FindSymbolControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Rubberduck.UI.FindSymbol"
mc:Ignorable="d"
d:DesignHeight="27" d:DesignWidth="270">
<UserControl.Resources>
<local:DeclarationImageConverter x:Key="DeclarationImageConverter" />
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="local:FindSymbolControl.GoCommand"
Executed="CommandBinding_OnExecuted"
CanExecute="CommandBinding_OnCanExecute"/>
</UserControl.CommandBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<ComboBox IsEditable="True"
ItemsSource="{Binding MatchResults}"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
TextSearch.TextPath="IdentifierName">
<ComboBox.ItemTemplate>
<DataTemplate DataType="local:SearchResult">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image Height="16" Width="16" Margin="2,0,2,0" Source="{Binding Declaration, Converter={StaticResource DeclarationImageConverter}}" />
<TextBlock Margin="2,0,2,0" Text="{Binding IdentifierName}" FontWeight="Bold" MinWidth="140" />
<TextBlock Margin="2,0,2,0" Text="{Binding Location}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Column="1"
Command="local:FindSymbolControl.GoCommand">
<Image Height="16" Source="pack://application:,,,/Rubberduck;component/Resources/arrow.png" />
</Button>
</Grid>
</UserControl>
The problem is that it doesn't work reliably, and far from instinctively.
If I type something in the box that actually matches an item, nothing happens until I manually select that item in the dropdown. Like here, I typed "sleepD", the box autocompleted to "sleepDelay", but the command is still disabled:
Once I've selected the item in the dropdown, the command button gets enabled as expected (although the image on the button doesn't show up grayed-out when the button is disabled, so it's not exactly as obvious as I intended it to be).
(the screenshot isn't really showing it, but there's only 1 match for that search)
If I click the button at that point, it works as expected. The problem is that if I make a new selection from the dropdown after that, the text box gets cleared instead of displaying the item I selected, and there's a weird delay during which the box is displaying what appears to be selected whitespace - this only seems to happen when the previous selection was made after selecting a value in the dropdown while the search text matches multiple entries, like "Sleep" above.
After the box got cleared, I can make a new selection from the dropdown and it will work as expected (except the VBE won't actually activate the CodePane I'm setting the selection to, but that's a separate issue).
The command implementation simply raises a Navigate event that passes a Declaration to the code that owns the VM instance.
The Search method, for which I need to add a .Take(50) after the .Select, to limit the number of returned results and perhaps reduce the lag a bit:
private void Search(string value)
{
var lower = value.ToLowerInvariant();
var results = _declarations.Where(
declaration => declaration.IdentifierName.ToLowerInvariant().Contains(lower))
.OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
.Select(declaration => new SearchResult(declaration));
MatchResults = new ObservableCollection<SearchResult>(results);
}
private string _searchString;
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
Search(value);
}
}
private SearchResult _selectedItem;
public SearchResult SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
private ObservableCollection<SearchResult> _matchResults;
public ObservableCollection<SearchResult> MatchResults
{
get { return _matchResults; }
set { _matchResults = value; OnPropertyChanged(); }
}
}
There's also an IValueConverter involved, that takes the Declaration in the SearchResult and switches on the declaration's DeclarationType enum to return a pack uri that points to the .png image to use in the dropdown list.
Aaah found it. It was all in the XAML.
Right here:
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
That line doesn't belong there; binding the TextSearch.Text property instead...
TextSearch.Text="{Binding SearchString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Makes it all work as intended. No glitch, no lag. Well there is a lag when I first drop the dropdown, but that's another issue.
Lesson learned: when TextSearch is enabled on an editable combobox, don't bind the Text property, unless you want weird behavior.

Hold Event Longlistselector

Hej
I want to create a standard holdevent. When you hold an element, there would appear some options you could chose like a new list.
How do you create this, is it just simply done with a popup or is there a smarter way?
Extra
After finding the answer, see answer below, some nice info is:
Put the context creation inside the hold event.
Then you can change to different contextmenus depending on the item. You can get the item that was holded by the following
private void StackPanel_Hold(object sender, GestureEventArgs e)
{
ItemViewModel itemViewModel = (sender as StackPanel).DataContext as ItemViewModel;
string t = itemViewModel.LineOne;
}
And
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Height="78" Hold="StackPanel_Hold">
<TextBlock Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
A good link for easy implementation is also youtube link below, replicated here :
Youtube
A ContextMenu is one option..
http://blogs.msdn.com/b/msgulfcommunity/archive/2013/05/19/windows-phone-toolkit-context-menu-getting-selected-item-within-a-long-list-selector.aspx

wpf ListBox Control: simple GetSelectedValue / item-value

List box is not binded just a Combobox replacement (values are exposed)
Xaml
<ListBox SelectionChanged="LBX_AddTaskOptions_SelectionChanged" HorizontalAlignment="Left" Margin="19,29,0,0" Name="LBX_AddTaskOptions" VerticalAlignment="Top" Width="125" FontWeight="Bold" Background="Beige">
<ListBoxItem Background="Beige" FontWeight="Bold" v>
<StackPanel Orientation="Horizontal">
<TextBlock Text="internet"></TextBlock>
<Image Source="Images\IE_BlackRed.png" Height="30"></Image>
</StackPanel>
</ListBoxItem>
<ListBoxItem Background="Beige" FontWeight="Bold">
<StackPanel Orientation="Horizontal">
<TextBlock Text="localFolder"></TextBlock>
<Image Source="Images\Folder_Black.png" Height="30"></Image>
</StackPanel>
</ListBoxItem>
</ListBox>
CodeBehind
private void LBX_AddTaskOptions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var SelItm = LBX_AddTaskOptions.SelectedItem.ToString();
MessageBox.Show(Sel);
}
i have searched for that question, though answers are only for complex issues
as i am fresh .net Developer, i know all methods to extract DDL text/value
i even made extentions , though couldn't figure how to do this simple value extraction
shouldn't it be simple ?
messageBox shows the name of control (:
This isn't quite the right approach for XAML. You don't want to list out the markup for each item -- instead, use an ItemTemplate to define how it should look, and use bindings to render the actual item:
<ListBox SelectionChanged="LBX_AddTaskOptions_SelectionChanged" Name="LBX_AddTaskOptions">
<ListBox.ItemTemplate>
<ListBoxItem Background="Beige" FontWeight="Bold" v>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Image Source="Images\IE_BlackRed.png" Height="30" />
</StackPanel>
</ListBoxItem>
</ListBox.ItemTemplate>
</ListBox>
Bind the ListBox ItemsSource to the model data itself (ie, the array of strings in this case). Now, eventually you'll probably want to use a view model, but you can also add the items from code behind on load:
string[] ListBoxItems = new string[] { "internet", "local folder" };
LBX_AddTaskOptions.ItemsSource = ListBoxItems;
This should result in SelectedValue giving you the correct value.
Footnote -- you could get the selected value using the markup you've written out in the question -- but it would be ugly and would defeat the whole purpose of XAML. You'd need to cast SelectedItem to a ListBoxItem, then get its child and cast that to a StackPanel, get its children, etc, you get the idea. And then, of course, if the markup changes at all, the code you just wrote is no longer valid.
The item that you are getting in your selected value is a ListBoxItem with a control inside it. If you want to extract the value like the text then you have to do this
private void LBX_AddTaskOptions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var SelItm = LBX_AddTaskOptions.SelectedItem as ListBoxItem;
var StackPanel = SelItm.Content as StackPanel;
foreach (var child in StackPanel.Children)
{
if(child is TextBlock)
{
MessageBox.Show((child as TextBlock).Text);
}
}
}
You have to sort of dig into the control to get the actual text. There are a lot of ways to get the value but this is the pretty basic one.
Calling ToString() method will just convert the current object as a string which is a ListBoxItem.

WPF, property bound to dependent ComboBox always gives initial value

I have a ComboBox that needs to depend on the value of another ComboBox. This part already works, with the dependent ComboBox refreshing when a new value is chosen in the independent ComboBox:
<!-- Independent -->
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="2"
x:Name="cbo_product" VerticalAlignment="Center" Width="120"
ItemsSource="{Binding Source={StaticResource productsXml}}"
DisplayMemberPath="#name" SelectedValuePath="#name"
SelectionChanged="cbo_product_SelectionChanged"
SelectedValue="{Binding Path=Product}" />
<!-- Dependent -->
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="2"
x:Name="cbo_component" VerticalAlignment="Center" Width="201"
DataContext="{Binding SelectedItem, ElementName=cbo_product}"
ItemsSource="{Binding XPath=Components/Component}"
DisplayMemberPath="#name" SelectedValuePath="#name"
SelectedValue="{Binding Path=Component}"
SelectionChanged="cbo_component_SelectionChanged" />
In the C# class behind this, I have:
public MyUserControlConstructor()
{
MyViewModelInstance= new MyViewModel();
DataContext = MyViewModelInstance;
}
And in MyViewModel, I have:
public string Component
{
get { return _component; }
set
{
if (value == _component)
{
return;
}
_component = value;
onPropertyChanged(PropertyNames.Component);
}
}
private void onPropertyChanged(PropertyNames fieldName)
{
if (null == PropertyChanged)
{
return;
}
string propertyName = Enum.GetName(typeof(PropertyNames), fieldName);
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
When I change the dependent ComboBox (Component), it shows up with the new value in my app, of course. However, when I hit a button that causes the value of the Component property to be displayed, it is always the initial value, and not the value I just chose in the ComboBox. I think there must be an error in my XAML. For the C#, I tried to follow a combination of this and this guide. How do I tie my dependent ComboBox to XML values nested in the SelectedItem of the independent ComboBox, but still update the Component property in my class?
Edit: my suspicion is that things are wonky because I set the DataContext for the dependent ComboBox in two places: first in the constructor in C#, to my view model, and second in the XAML, to DataContext="{Binding SelectedItem, ElementName=cbo_product}".
Edit: I had been setting initial values in the constructor to my view model class. When I take out the initial value for the Component property, then even after I change the selected value in the dependent ComboBox, I still get no value from the Component property. This pretty much just rehashes what I already knew: the dependent ComboBox is tied to the independent ComboBox (it gets its data from the independent ComboBox, that is), but not to the Component property.
Edit: by request, here's a sample of my XML:
<Products xmlns="">
<Product name="Awesomeness">
<Components>
<Component name="Component of Glory"/>
<Component name="Component of Doom"/>
</Components>
</Product>
</Products>
Edit: I'm guessing a MultiBinding would be of use, after looking at this and this.
Edit: it seems like I should be able to get the dependent ComboBox to work without setting DataContext, just by using ItemsSource:
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="2"
x:Name="cbo_component" VerticalAlignment="Center" Width="201"
ItemsSource="{Binding ElementName=cbo_product, Path=SelectedItem,
XPath=Components/Component}"
DisplayMemberPath="#name" SelectedValuePath="#name"
SelectedValue="{Binding Path=Component}"
SelectionChanged="cbo_component_SelectionChanged"/>
However, this doesn't work: the dependent ComboBox is empty, instead of showing all the Component names.
The way I found of getting around this involves setting the ItemsSource in C# instead of XAML, which I would prefer not to do. However, it works, and after a day and a half of banging on this, it's the best I came up with.
In XAML:
<!-- Independent -->
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="2"
x:Name="cbo_product" VerticalAlignment="Center" Width="120"
ItemsSource="{Binding Source={StaticResource productsXml}}"
DisplayMemberPath="#name" SelectedValuePath="#name"
SelectionChanged="cbo_product_SelectionChanged"
SelectedItem="{Binding Path=ProductNode}"
SelectedValue="{Binding Path=Product}" />
<!-- Dependent -->
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Row="3" Grid.Column="2"
x:Name="cbo_component" VerticalAlignment="Center" Width="201"
DisplayMemberPath="#name" SelectedValuePath="#name"
SelectedValue="{Binding Path=Component, Mode=TwoWay}"
SelectionChanged="cbo_component_SelectionChanged"/>
In C#, the event handler for when the independent ComboBox changes:
private void cbo_product_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
// Set ItemsSource of dependent ComboBox
cbo_component.ItemsSource = getChildNodesFromComboBox(
sender as ComboBox, "Components/Component"
);
cbo_component.SelectedIndex = 0;
}
// Helper method to do XPath query and get child nodes from selected value of
// independent ComboBox
private XmlNodeList getChildNodesFromComboBox(ComboBox comboBox,
string xpath)
{
if (null == comboBox)
{
return null;
}
var xml = comboBox.SelectedItem as XmlElement;
if (null == xml)
{
return null;
}
return xml.SelectNodes(xpath);
}
Now the Component property in my view model class, to which my dependent ComboBox is bound in XAML, gets populated with the value selected in the dependent ComboBox because I didn't have to change the DataContext of the dependent ComboBox.

Categories

Resources