InvalidCastException in ComboBox SelectionChanged event handler - wpf

This code works well:
private void Combobox1_Loaded(object sender, RoutedEventArgs e)
{
var combo = (ComboBox)sender;
var pointGroupList = (List<PointGroup>)combo.ItemsSource;
combo.ItemsSource = pointGroupList.Select(group => group.Name);
}
But this one doesn't work at all:
private void Combobox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var combo = (ComboBox)sender;
var pointGroupList = (List<PointGroup>)combo.ItemsSource;
textBlock1.Text = "num of points:" + pointGroupList.Find(group => group.Name == (string)combo.SelectedItem).PointsCount.ToString();
}
Here's the message in my output window:
System.InvalidCastException: Unable to cast object of type 'WhereSelectListIterator2[Autodesk.Civil.DatabaseServices.PointGroup,System.String]' to type 'System.Collections.Generic.List1[Autodesk.Civil.DatabaseServices.PointGroup]'. at _01_COGO_Points.ModalDialog_1.Combobox1_SelectionChanged(Object sender, SelectionChangedEventArgs e) in D:\00 Materials\c3d\c#\examples\ACAD\01 COGO Points\Window.xaml.cs:line 49
Any help will be appreciated.

What you're doing in your Loaded event is quite weird. I would not recommend doing that, as it will wreck your bindings. If the reason you're doing that is so that the Name property is shown in your ComboBox, you should rather use a DataTemplate. Something like this:
<Window.Resources>
<DataTemplate x:Key="pntGroupTemplate" DataType="{x:Type ac:PointGroup}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Window.Resources>
and you will of course need to add a namespace to your Window. Something like this:
xmlns:ac="clr-namespace:Autodesk.Civil.DatabaseServices;assembly=AeccDbMgd"
I don't have Civil, so not sure if that's exactly right, but it should be close. Intellisense should be able to help you with the correct path if this one is not quite right.
and in your combobox,
<ComboBox ItemTemplate="{StaticResource pntGroupTemplate}" ... />
My best advice is to just completely get rid of all the code in your Combobox1_Loaded event handler and create a DataTemplate in xaml to display the Name property using the code snippet above. And lastly, change your lambda expression from this:
group => group.Name == (string)combo.SelectedItem
to this:
group => group.Name == (combo.SelectedItem as PointGroup)?.Name
The exception you're getting is due to the second line. When you call the Select method in the Loaded event, it returns IEnumerable<string>, so when you're casting the ItemsSource to List<PointGroup> that's where everything goes sideways in so many different ways :-).
Another issue with what you're doing, is that now, SelectedItem is a string, and does not have a Name property.
Hope that helps

Related

WPF Getting the content of a ListViewItem

I have a school assignment: when a ListViewItem gets double-clicked, it's content has to be displayed in the MessageBox. I've done quite a lot of research on the internet and couldn't find a fitting solution to my problem. What is the easiest way to get the content of a ListViewItem?
The ListView looks like this:
<ListView Name="LV">
<ListViewItem>a listview</ListViewItem>
<ListViewItem >with several</ListViewItem>
<ListViewItem>items</ListViewItem>
</ListView>
When you have explictly assigned ListViewItem instances to the ListView (as in the example in the question), you can get each ListViewItem's content by means of its Content property.
You may access it in a MouseDoubleClick event handler.
<ListView x:Name="LV" MouseDoubleClick="LV_MouseDoubleClick">
<ListViewItem>a listview</ListViewItem>
<ListViewItem>with several</ListViewItem>
<ListViewItem>items</ListViewItem>
</ListView>
The event handler in code behind:
private void LV_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var item = (ListViewItem)LV.SelectedItem;
var content = (string)item.Content;
MessageBox.Show(content);
}
There is however a better way to get the Content of the SelectedItem.
Set the SelectedValuePath property
<ListView x:Name="LV" MouseDoubleClick="LV_MouseDoubleClick"
SelectedValuePath="Content">
<ListViewItem>a listview</ListViewItem>
<ListViewItem>with several</ListViewItem>
<ListViewItem>items</ListViewItem>
</ListView>
and get SelectedValue in code behind:
private void LV_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var content = (string)LV.SelectedValue;
MessageBox.Show(content);
}
As a note, your ListView should be a ListBox, the base class of ListView. You would only use a ListView when you also set its View property.

WPF: Get object visualized by (hierarchical) data template

I've got a TreeView with a couple of items in it. The items are visualized by a simple hierarchical data template, like this:
<HierarchicalDataTemplate x:Key="instanceTemplate">
<CheckBox Checked="InstanceCheckChanged" Unchecked="InstanceCheckChanged">
<Label>Hello World!</Label>
</CheckBox>
</HierarchicalDataTemplate>
As you can see I've added an event handler, here's the code behind:
private void InstanceCheckChanged(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
}
In this event handler, the sender of the event is obviously the check box itself, however the checkbox is actually visualizing my normal object. My question is, how do I get the object that the checkbox visualized? Preferably I would like to have a method with a signature like this:
public MyObject GetMyObject(UIElement sender);
Is this possible in WPF, or is there a clean way to store some metadata so that I know which MyObject was checked?
Your CheckBox's DataContext will be the object that it's representing:
var myObject = ((CheckBox)sender).DataContext as MyObject;

Silverlight DataTrigger not firing on load

I'm attempting to convert some of my WPF skills to Silverlight, and have run into a slightly odd problem in the test mini-app I've been working on. In WPF, I got used to using DataTriggers within a style to set up control properties based on properties of the bound data. I discovered that some assemblies related to Blend allow you to do something like this in Silverlight, and I came up with something like this, in which I've got the following namespaces declared:
xmlns:ia="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:iv="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<DataTemplate x:Key="testItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Name, Mode=TwoWay}" x:Name="thing"/>
<iv:Interaction.Triggers>
<ia:DataTrigger Binding="{Binding Name}" Value="ReddenMe" Comparison="Equal">
<ia:ChangePropertyAction TargetName="thing" PropertyName="Foreground" Value="Red">
</ia:ChangePropertyAction>
</ia:DataTrigger>
</iv:Interaction.Triggers>
</StackPanel>
</DataTemplate>
In this example, I've got a data object implementing INotifyPropertyChanged and raising the PropertyChanged event as usual for the Name property. I get the expected behaviour if I change the value of the textbox and lose focus, but if the initial value of the textbox is set to ReddenMe (which for this contrived example I'm using as the trigger for the text to be red), the text doesn't go red. Does anybody know what's going on here? For DataTriggers in WPF, the trigger would be fired immediately for any data.
I realise that I could use a Converter here, but I can think of situations where I'd want to use triggers, and I wonder if there's anything I could do to make this work.
Here's a solution I found on Tom Peplow's blog: inherit from DataTrigger, and make the trigger evaluate the condition when its associated element is loaded.
Here's how you can code it:
public class DataTriggerEvaluateOnLoad : Microsoft.Expression.Interactivity.Core.DataTrigger
{
protected override void OnAttached()
{
base.OnAttached();
var element = AssociatedObject as FrameworkElement;
if (element != null)
{
element.Loaded += OnElementLoaded;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
var element = AssociatedObject as FrameworkElement;
if (element != null)
{
element.Loaded -= OnElementLoaded;
}
}
private void OnElementLoaded(object sender, RoutedEventArgs e)
{
EvaluateBindingChange(null);
}
}

WPF expand TreeView on single mouse click

I have a WPF TreeView with a HierarchicalDataTemplate.
Currently I have to double click an item to expand/collapse it.
I would like to change this behaviour to a single click, without loosing other functionality. So it should expand and collapse on click.
What is the recommended way to do this?
Thanks!
You could use a re-templated checkbox as your node (containing whatever template you are currently using) with its IsChecked property bound to the IsExpanded property of the TreeViewItem.
Here is a template I've just test that seems to do the job:
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsExpanded}">
<CheckBox.Template>
<ControlTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</ControlTemplate>
</CheckBox.Template>
</CheckBox>
</HierarchicalDataTemplate>
Just replace the ControlTemplate contents with whatever you need.
If you are using a standard TreeViewItem, then you can capture the click event:
private void OnTreeViewMouseUp( object sender, MouseButtonEventArgs e )
{
var tv = sender as TreeView;
var item = tv.SelectedItem as TreeViewItem;
if( item != null )
item.IsExpanded = !item.IsExpanded;
e.Handled = true;
}
private void OnTreeViewPreviewMouseDoubleClick( object sender, MouseButtonEventArgs e )
{
e.Handled = true;
}
Most likely in your case, you'll need to do something with your binding and ViewModel. Here's a good article from CodePlex: Simplifying the WPF TreeView by Using the ViewModel Pattern.
Just use selected item changed event and use the following,
private void treeview_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem item = (TreeViewItem)treeview.SelectedItem;
item.IsExpanded = true;
}
where treeview is the name of your TreeView, you could include an if to close/open based on its current state.
I have very little experience working with WPF to this point, so I am not 100% certain here. However, you might check out the .HitTest method of both the Treeview and TreeView Item (the WPF Treeview is essentially the Windows.Controls.Treeview, yes? Or a derivation thereof?).
THe HIt Test method does not always automatically appear in the Intellisense menu for a standard Windows.Forms.Treeview (I am using VS 2008) until you type most of the method name. But it should be there. You may have to experimnt.
You can use the .HitTest Method to handle the MouseDown event and return a reference to the selected treeview item. You must test for a null return, however, in case the use clicks in an area of the control which contains no Tree Items. Once you have a reference to a specific item, you should be able to set its .expanded property to the inverse of whatever it is currently. again, some experimentation may be necessary here.
As I said, I have not actually used WPF yet, so I could have this Wrong . . .
The answer of Metro Smurf (thanks to which I got where I wanted to be) suggests the right approach . You could simply hook up to the SelectedItemChanged event of the Treeview. Then cast the e.NewValue passed in the eventhandler as TreeViewItem, and access its IsExpanded property to set it to true.
void MyFavoritesTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
((TreeViewItem)e.NewValue).IsExpanded = true;
}
Then for the final touch, you can also hook up the items in your Treeview by casting them as TreeViewItem as suggested, and then you can hook up to the various manipulation events, like:
var item = tv.SelectedItem as TreeViewItem;
item.Expanded += item_Expanded;
And then do whatever you need to do in the eventhandler
void item_Expanded(object sender, RoutedEventArgs e)
{
// handle your stuff
}

How do I detect row selection in the Xceed DataGrid for WPF

I'm horrible at this WPF thing, so bear with me.
I'm using the Xceed DataGrid for WPF, and I need to know when someone selects a row, but I can't figure out how to do it. I'm sure I need to add some XAML to enable this, but I can't figure out what I should do.
I use a MVVM approach and therefor favor data binding. I will bind the SelectedItem property to a SelectedItem property on my ViewModel object for the grid.
<xcdg:DataGridControl x:Name="grid" SelectedItem="{Binding SelectedItem}">
</xcdg:DataGridControl>
Then on your property setter can do what ever is necessary upon change in the SelectedItemChanged() method.
private IMyItem _selectedItem;
public IMyItem SelectedItem
{
get { return _selectedItem; }
set {
_selectedItem = value;
OnPropertyChanged("SelectedItem");
SelectedItemChanged();
}
}
I'm actually struggling a bit with the same thing myself, except I have a prerequisite that the selection notification be done via an ICommand; however, if you do not have this need, you can wire up the SelectionChanged event handler. It's pretty elementary stuff, but I'll include the code just in case:
XAML:
<Grid>
<DataGrid:DataGridControl x:Name="gridControl" SelectionChanged="gridControl_SelectionChanged">
<!-- Content -->
</DataGrid:DataGridControl>
</Grid>
Code-behind:
private void gridControl_SelectionChanged(object sender, Xceed.Wpf.DataGrid.DataGridSelectionChangedEventArgs e)
{
var selectedIndex = gridControl.SelectedIndex; // int index
var selectedItem = gridControl.SelectedItem; // instance of bound object
var selectedItems = gridControl.SelectedItems; // IList of bound objects
}
All that said, I'm very interested to hear if there are any elegant solutions for getting the selected row from an Xceed DataGrid with an ICommand (in my case, I'm using anonymous types, which can make a difference)...
You don't have to write complicated code for something simple... although it can become tedious, here is some code for you. I hope this helps:
<Style TargetType="xcdg:DataRow">
<EventSetter Handler="dr_PreviewMouseDown" Event="PreviewMouseDown" />
</Style>
void dr_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
DataRow dr = sender as DataRow;
Debug.WriteLine(sender);
}
So here's what I came up with
System.ComponentModel.DependencyPropertyDescriptor gridItemsSourceDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(DataGridControl.SelectedItemProperty, typeof(DataGridControl));
gridItemsSourceDescriptor.AddValueChanged(dgBaxRuns, HandleSelectionChanged);
I made for me a easiest way.
<xctk:MaterialButton Margin="5,0,5,0" Grid.Column="3" Content="Szűrt sorok kijelölése" Command="{Binding SelectFilteredRowsCommand}" CommandParameter="{Binding ElementName=MyDataGrid}" />
So, i send my datagrid with my commandparameter to the viewmodel.
public RelayCommand<object> SelectFilteredRowsCommand { get; set; }
SelectFilteredRowsCommand = new RelayCommand<object>((o) =>
{
var datagrid = o as DataGridControl;
if (datagrid != null)
{
var datagriditems = datagrid.Items.Cast<SelectableProduct>();
foreach (SelectableProduct selectableProduct in datagriditems)
{
selectableProduct.IsSelect = true;
}
}
});
And convert back to datagrid itemsoruce type.

Resources