I have a listbox with a datatemplate that holds a number of controls bound to my collection.
What i want is to change the itemsource for one of these comboboxes dependant upon the value selected in one of the other comboboxes in the same row. I don't want to change the itemsource for all corresponding comboboxes within the rest of the rows in the listbox.
How do I get a handle on the control in the selected row only.
Is this something that is easier to try doing witht the WPF datagrid?
Thanks.
This is actually easier with the ListBox, as the DataTemplate defines all the controls for a row.
I think the easiest way is to use a converter on a binding. You will bind your second ComboBox's ItemsSource to the SelectedItem of the first ComboBox:
<myNamespace:MyConverter x:Key="sourceConverter" />
<StackPanel Orientation="Horizontal>
<ComboBox x:Name="cbo1" ... />
...
<ComboBox ItemsSource="{Binding SelectedItem, ElementName=cbo1, Converter={StaticResource sourceConverter}}" ... />
...
</StackPanel>
Note that if you need additional information from the DataContext of the Row, you can make it a MultiBinding and an IMultiValueConverter, and pass in the DataContext easily by doing:
<MultiBinding Converter="{StaticResource sourceConverter}">
<Binding />
<Binding Path="SelectedItem", ElementName="cbo1" />
</MultiBinding>
Then, in your converter class, do whatever it is you have to do in order to get the correct items source.
Get the SelectionChanged event of that paticular combobox and set the Itemsource of the other combobox inside the event.
private void cmb1SelectionChanged(object sender, SelectionChangedEventArgs e)
{
cmboBox2.ItemSource = yourItemSource;
}
Also it is better to get the SelectionChaged event of listview and handle it.
private void OnlistviewSelectionChanged( object sender, SelectionChangedEventArgs e )
{
// Handles the selection changed event so that it will not reflect to other user controls.
e.Handled = true;
}
Related
I have two DataGrids filled with separate collections of objects of the same type. In my ViewModel I have one property "CurrentObject" that I want to bind to the currently selected object.
That means that if I select a row in DataGrid A the CurrentObject holds the selected Item of DataGrid A and the if i select a row in DataGrid B the CurrentObject holds the selected Item of DataGrid B.
In both DataGrids I've done the binding like this:
<DataGrid SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CurrentObject}">
It only works as expected when I change the selection within one DataGrid. If I select a already selected row the CurrentObject property isn't updated and still references to the object from the other DataGrid. I guess it's because the SelectionChanged event isn't fired then
Clear SelectedItem in DataGrid's LostFocus Event:
example:
private void dg1_LostFocus(object sender, RoutedEventArgs e)
{
dg1.SelectedItem = null;
}
private void dg2_LostFocus(object sender, RoutedEventArgs e)
{
dg2.SelectedItem = null;
}
because if you don't clear SelectedItem when the DataGrid lostfocus, the DataGrid will still remember SelectedItem , and if you select this item again, the SelectionChanged event will not fire.
mvvm example:
public void dg1_LostFocus(object sender, RoutedEventArgs e)
{
DataGrid dg = sender as DataGrid;
if(dg != null)
{
dg.SelectedItem = null;
}
}
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<ei:CallMethodAction MethodName="dg1_LostFocus" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
I can't clearly tell whether you're using events or bindings, but I'll suppose you do it the binding way. In that case, try setting IsSynchronizedWithCurrentItem to true. More info here
Rang also had the idea to use Interaction Triggers to avoid code behind. Looks like we had the same idea at the same time. His approach is to handle the LostFocus event in an extra view models method, mine is to handle the GotFocus with binding. I think it's a personally preference which way to go. For the sake of completeness:
<iy:Interaction.Triggers>
<iy:EventTrigger EventName="GotFocus">
<is:ChangePropertyAction TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext}"
PropertyName="CurrentObject"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=SelectedValue}" />
</iy:EventTrigger>
</iy:Interaction.Triggers>
Since the Binding is only updated when the selection is changed within a single control, I additionally change the property when the DataGrid got the focus
I have developed a WPF UserControl that is intended to be used as a pick list as follows:
A DataGrid bound to a CollectionView of entities (e.g. of Employees)
A TextBox above the DataGrid that can be used to filter items displayed in the DataGrid.
I want to expose a Command that will be executed when the user double-clicks on a row in the DataGrid. The container can then react to this by doing something with the SelectedItem in the DataGrid.
So far I've tried to handle the double-click as follows:
<DataGrid IsReadOnly="True">
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="... />
</DataGrid.InputBindings>
...
However the double-click event still fires when the user clicks in the DataGrid header. I'd like to be able to limit it so that the Command is only executed when the double click is in the body of the DataGrid. Is there a declarative way to do this?
UPDATE
I'm just getting to grips with WPF and MVVM, and am really looking for guidance on how to implement low-level reusable components like this. Any general advice will also be gratefully received and upvoted. As it stands, I'm assuming I will want this UserControl to:
Expose a dependency property "SelectedItem" that is bound to the DataGrid's SelectedItem
Expose a RoutedEvent "ItemDoubleClick" or similar that is fired when the user double-clicks on a row.
Implement ICommandSource and call CommandHelpers.ExecuteCommandSource(this) from the row double-click event handler.
If code behind is not a problem:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="Loaded" Handler="Row_Loaded"/>
</Style>
</DataGrid.RowStyle>
private void Row_Loaded(object sender, RoutedEventArgs e)
{
var row = sender as DataGridRow;
row.InputBindings.Add(new MouseBinding(MyCommands.MyCommand,
new MouseGesture() { MouseAction = MouseAction.LeftDoubleClick }));
}
You can simply put the DataGrid into a Grid and define your InputBindings in the Grid. In the canExecute-definition, you should check, if a row is selected. That works for the KeyBinding as well, for example a custom Delete-Command.
<Grid>
<Grid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="... />
</Grid.InputBindings>
<DataGrid IsReadOnly="True">
...
</Grid>
I created programatically a class (I called it ViewGrid) so that I use an instance of it as ItemTemplate for my ListBox control; of course, it's my data template for the listboxitem....
Also, in my ViewGrid class, I got a dependency property called IsChecked and I want to keep it in sync with the ListBoxItem's IsSelected property. I noticed that in SL there no relativesource-findancestor-ancestortype support for binding as in WPF, still, I need to find a way to keep my IsChecked property synchronized with the IsSelected property of the internally generated ListBoxItem for my ListBox control. Can you help?
Here is a ListBox defined in XAML that uses the IsSelected property of each LitBoxItem to show or hide a button when selected. You just need to duplicate that Binding approach for the ListBoxItems you create in code. Either that, or create a UserControl with the appropriate ListBoxItem XAML, and insert instances of those UserControls into your ListBox.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200" Height="120">
<StackPanel Margin="5">
<TextBlock Text="{Binding Name, Mode=OneWay}" />
<StackPanel Visibility="{Binding IsSelected, Mode=OneWay, Converter={StaticResource BoolToVisible}}">
<Button Content="Show Details" Click="OnDetailsClick" Tag="{Binding}" />
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Good luck,
Jim McCurdy
Face To Face Software and YinYangMoney
UPDATE: I revisited this and found a much better solution. My original one remains below, but the way I actually ended up solving this problem is via using the ViewGrid in a ControlTemplate instead of a DataTemplate. Then you can use the RelativeSource TemplatedParent binding to bind to the IsSelected property of the ListBox. So, add the following to the Resources of the listbox or your page or user control:
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel>
<ViewGrid IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
<!-- other controls may go here -->
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ORIGINAL:
So after seven years, you almost certainly don't need an answer to this anymore... however, I recently spent a morning wrestling with this issue and thought I'd give my solution in case any similar unfortunate ends up here.
First off, anyone who's using Silverlight 5 is in luck as AncestorType is apparently now available for RelativeSource, letting you bind directly to the IsSelected property of the ListBoxItem. For those of us stuck with 4 or below, the only real workaround I came up with was "faking" the binding via use of events in the code behind.
To do this, assume you have your YourView XAML with a ListBox named "lbYourListBox" which has its ItemsSource and SelectedItem properties bound to appropriate properties on a YourViewModel class, along with a ViewGrid in its ItemTemplate whose IsChecked property is not bound to anything. Then, in your code behind file, you wire up events as follows:
public YourView()
{
InitializeComponent();
this.Loaded += (sender, e) =>
{
((YourViewModel)this.DataContext).PropertyChanged += vm_PropertyChanged;
UpdateViewGrids();
};
}
// this part propagates changes from the view to the view model
private void viewGrid_Checked(object sender, RoutedEventArgs e)
{
var selectedVM = ((ViewGrid)sender).DataContext as SourceItemType;
((YourViewModel)this.DataContext).SelectedViewGridItem = selectedVM;
}
private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.Equals(e.PropertyName, "SelectedViewGridItem"))
{
UpdateViewGrids();
}
}
// this part propagates changes from the view model to the view
private void UpdateViewGrids()
{
var viewGrids = this.lbYourListBox.GetVisualDescendants().OfType<ViewGrid>();
var selectedVM = ((YourViewModel)this.DataContext).SelectedViewGridItem;
foreach (var grid in viewGrids)
{
grid.IsChecked = selectedVM == grid.DataContext;
}
}
The viewGrid_Checked event handler should be wired up to the Checked event of the view grid in the ItemTemplate. The GetVisualDescendants() method comes from the Silverlight Toolkit.
Important caveats:
The ViewGrid.Checked event should not fire except for the unchecked->checked transition, and no more than one view grid should be able to be selected at once. If those two things aren't true, you'll have to make appropriate edits to ensure this code can't cause an infinite event-driven loop. (Of course, if you don't need two-way binding, you only need one of these event handlers and event ping-pong isn't a concern.)
I wrote this for a user control which had its data context set in XAML, which is why the event handler for the view model's PropertyChanged event is only assigned after the view is loaded. Depending on how and when your view and view model are bound to each other, you may have to assign that earlier/later/differently.
This won't work if the view grids aren't visible, GetVisualDescendants seems to ignore hidden/collapsed controls.
I have a ListView binding to an ItemsSource and the SelectionChanged event is firing on the load/databound events? I assume that it is because a 'default' items ie index 0 is selected.
How can I disable this?
The listView should not fire the SelectionChange if you only set the ItemsSource property. However if you bind the SelectedIndex property to a property of your dataContext object the selection will move to the index that is specified by the binded property.
this doesn't fires the Selector_OnSelectionChanged event when the page loads:
<ListView SelectionChanged="Selector_OnSelectionChanged"
ItemsSource="{Binding Path=Items}"
></ListView>
but this does:
<ListView SelectionChanged="Selector_OnSelectionChanged"
SelectedIndex="{Binding Path=SelectedIndexValue}"
ItemsSource="{Binding Path=Items}"
></ListView>
because the SelectedIndex is set to the SelecteIndexValue through binding.
To avoid this and still keep the bindings in your markup set the SelectedIndexValue of your dataContext object to -1 before binding (Before InitializeComponent() is called in your form constructor).
Hope this helps.
thanks for the responses.
When I put a breakpoint on the SelectionChanged event, it breaks proceedings there before the screen is fully loaded. You will also see that the first row is 'selected' afterwards on the list. I am not binding to a SelectedIndexValue as you can see in the code. The DataContext for the list is a ReadonlyCollection
In my SelectionChanged event as you can see I notify other objects to be loaded with data relating to the selected item. I only want this to happen when one is selected but not a default one to be set. I have to of these ListViews representing similar data but on loaded none must have an item selected.
I have noticed that the default Selected index is set to -1 on the properties window for the Listview. I can even set this is code on the List_Loaded event, but by then the first SelectionChanged has happened already.
<ListView PreviewMouseDown="ActiveCasesView_MouseDown" x:Name="ActiveCasesView"
DataContext="{StaticResource ActiveCasesViewSource}"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource CasesItemTemplate}"
SelectionMode="Single"
SelectionChanged="ActiveCasesView_SelectionChanged"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto" >
</ListView>
private void ActiveCasesView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (mouseClicked)
if (e.AddedItems.Count > 0)
App.Messenger.NotifyColleagues(App.MSG_SELECT_ACTIVE_CASE, ((CaseViewModel)ActiveCasesView.SelectedItem).CaseNumber);
}
I added the PreviewMouseDown to set an indicator that I have clicked on the listview in the SelectionChanged event. This does help but I'm not convinced that its the best solution.
Thanks
Petrus
I don't know if you still need help with this, but I solved this problem by making a variable that tracks the selectedindex, in my case -- when initially bound it's always 0 so it's a little easier for me to do, however if you inform the viewmodel of the appropriate index, I simply added a
ComboBox box = e.OriginalSource as ComboBox;
if (_previousIndex == cb.SelectedIndex) return;
//do stuff you need to do with a new SelectedIndex
You can try to set the SelectedIndex property to -1 in your binding but this also is not an elegant solution.
<ListView PreviewMouseDown="ActiveCasesView_MouseDown" x:Name="ActiveCasesView"
DataContext="{StaticResource ActiveCasesViewSource}"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource CasesItemTemplate}"
SelectionMode="Single"
SelectionChanged="ActiveCasesView_SelectionChanged"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
**SelectedIndex="-1"**>
</ListView>
I tried to reproduce your behavior but without success.
What is the Type of the ItemsSource collection that you are binding to?
you can use window loaded event to block the action
bool loaded = false;
window.Loaded += new RoutedEventHandler(MainWindow_Loaded);
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
windowLoaded = true;
}
private void ActiveCasesView_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if(!loaded)
return ;
//do actions here ....
}
I have a TextBox and a ComboBox. I want to bind ComboBox the selected value to text in the TextBox.
Please help.
Thanks
<ComboBox x:Name="MyComboBox">
<ComboBoxItem>12</ComboBoxItem>
<ComboBoxItem>13</ComboBoxItem>
<ComboBoxItem>14</ComboBoxItem>
<ComboBoxItem>15</ComboBoxItem>
</ComboBox>
<TextBox Text="{Binding Path=SelectedValue.Content, ElementName=MyComboBox}" />
Since the items in the ComboBox are of type ComboBoxItem, I used the Content property to get the real value. You should use whatever property exposed by the objects in your ComboBox (use nothing if it already is a list of strings).
This is for a listbox, not a combobox, but it should be pretty much the same code:
private void *lstProducts*_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
*currentlabel*.Content = *lstProducts*.SelectedValue.ToString();
}
The italicized bits are the names of the control.
Hope it helps...