When a new item is added to the Flights collection a new TabItem is added to the TabControl. When a new tab is added, I need to call a method on the Chart control. The problem is I can't figure out the right event to handle.
My XAML looks something like the following:
<TabControl Name="chartControl" ItemsSource="{Binding Flights}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<WindowsFormHost Name="winHost">
<legacy:Chart></legacy:Chart>
</WindowsFormHost>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I tried handling the Loaded on the TabControl,
but duh that's only fired once.
I attempted a DataTemplate
Trigger on the RoutedEvent
FrameWorkElement.Loaded but I'm pretty sure that's not meant for my situation
I tried an EventSetter but that
didn't quite work the way I want
either
I attempted a few other things, but I don't quite remember them all.
Any suggestions would be greatly appreciated!
If I'm reading your XAML correctly, you are creating a single Chart control for the TabControl and changing its data when the TabItem changes? If so, you should be able to use the SelectionChanged event.
You might be better off putting your Chart control in the ItemTemplate so it automatically loads the selected Flights data when the user switches tabs or adds a new one.
Your Flights collection should be of type ObservableCollection<>. The ItemsSource binding in xaml will subscribe to its CollectionChanged event and add/remove tabs. As for calling the method on the Chart, does the WindowsFormHost have a Loaded event? Because a new one will be created for each tab that's created.
Related
I have a couple specific user controls to Show some Content, e.g. simple like Image, WebControl but also two complex specific custom controls drawing on a canvas.
Now I thought using the DataTemplateSelector to handle the different UserControls. I actully used this http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector as a reference.
I changed the code so the form loads the UserControls dynamically (according to the file extension) in the following collection:
ObservableCollection<string> _pathCollection = new ObservableCollection<string>();
The only difference to the reference is now I want to navigate back and forward to the next control by showing one control only at the time. Which control should I use instead of ListView?
<Grid>
<ListView ScrollViewer.CanContentScroll="False"
ItemsSource="{Binding ElementName=This, Path=PathCollection}"
ItemTemplateSelector="{StaticResource imgStringTemplateSelector}">
</ListView>
</Grid>
How do I need to bind it to the template (equal to ItemTemplateSelector above)? WPF is still very new to me and I am learning.
Use a ContentControl. Bind your current item to the Content-property and the DataTemplateSelector to the ContentTemplateSelector-property.
<ContentControl Content="{Binding Path=CurrentItem, Mode=OneWay}", ContentTemplateSelector="{StaticResource imgStringTemplateSelector}" />
Your CurrentItem should be a DependencyProperty or a INotifyPropertyChanged-property of your DataContext. When you change your CurrentItem, the ContentControl will update the template automatically with help of your TemplateSelector.
I have a WPF/MVVM app with a ListBox which displays data through a DataTemplate. I managed to change the selected item in the ListBox when pressing a button so the CommandParameter is linked to the ListBox's SelectedItem, but I cannot get the buttons to be enabled/disabled correctly in the same way. For example, if I have 2 items and the button should be enabled in one and disabled in the other, when I select an element BOTH buttons have the same state, and they BOTH change state when I select another item.
I am using a RelayCommand as used in many MVVM Frameworks.
Here is my XAML (removed "not interesting" parts):
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<Grid>
<Button Content="Something" Name="EnabledDisabledButton" Click="Button_Click"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SomeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=SelectedItem}"/>
</Grid>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
<Setter Property="ContentTemplate" Value="{StaticResource ItemTemplate}" />
</Style>
</UserControl.Resources>
<ListBox x:Name="myListBox" ItemsSource="{Binding ElementList}"
IsSynchronizedWithCurrentItem="True" ItemContainerStyle="{StaticResource ContainerStyle}"/>
I tried to pass the SelectedItem as a parameter to the RelayCommand's CanExecute method, but the result was the same as before.
Is there a way to pass the actual ListBoxItem in which the button "lives in" as a parameter to the command, so each one will be processed separately by the CanExecute method? Would it work if I got this? (right now I am handling the Click event to select the correct item in the list before executing the command).
In my CanExecute method I am evaluating some property of the SelectedItem in order to enable/disable the corresponding button. An alternative would be to evaluate this property for all elements, but I cannot think of a way to do it inside the ViewModel, and then communicate to the view the result (if it is even possible while using a DataTemplate for the items).
Thanks for your input, regards!
Converting My comment into an answer:
Why not just CommandParameter="{Binding}"?
You mention "MVVM" in the question, but it seems you use the MVVM way to your full advantage.
I would not have a Button_Click event in the style at all. That is because it is in fact a style, which per definition could be changed to another style which does not have the same event, which again will make the application stop working as wanted if you choose to have a style-based app in the future.
A rule I use is that a style is a style. A style has to do with the UI and "looks" of the app.
Functionality should be separate from the UI. The programmer can define the Command, and the designer can decide how the user will use that in the best way.
That's exactly where the code separation from the MVVM pattern cames into grip.
To separate the "looks" and user behavior and the app's logic.
Like...it should not matter to the model if a command fires from a button, a menu, a datacontext or a key stroke.
If this particular problem was handled to ME, I would solve it by having a HOLDER-class.
This is a class (DependencyObject which implements INotifyPropertyChanged) that holds a ICommand property as well as the "row" that will be displayed in the various rows in the ListBox.
The ICommand property will be bound to the Button, having the row (class) itself as CommandParameter to the call.
Then the actual row would be used in the ItemTemplate on the ListBox, with Bindings to different elements (proprty with or withouy Converters) to make whatever desired display available.
I hope I explained good enough...
Feel free to ask more if you want more details to my solution alternative.
I want to double-click to copy an item from a "shopping list" to a "shopping cart" list box. Right now, my model just has ObservableCollection of strings for each list, but eventually the objects will get more complex.
The ViewModel is mapped to the view using a DataTemplate. Right now, I just have a "Session" property on my ViewModel that is exposing my Session object in my Model that contains both ObservableCollections.
I tried this...
<ListBox Name="listBoxShopList" ItemsSource="{Binding Path=Session.Products}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItemMouseDoubleClick"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ListBox Name="listBoxCart" ItemsSource="{Binding Path=Session.CartItems, UpdateSourceTrigger=PropertyChanged}"/>
From code-behind I do get the event and I can get the SelectedItem. But being new to MVVM, I cannot figure out how to add the item to the "Cart" collection. It seems like I should be able to access the ViewModel Session.CartItems directly since the View can. Is a parameterized command the way to go? If so, any recommended articles?
In your event handler code-behind you could get the ViewModel like this:
var viewModel = DataContext as <YourViewModelType>;
And then transfer the selected item to the cart.
The preferred way to do it would be using a command, like DelegateCommand.
Well, you get your handler (a part of view code) called on double click. Good so far.
Now, you need to inform the VM that double-click happened (or better put some semantics here: selection changed, shopping cart accepted, etc.) by invoking a command (preferred way), or communicating with the VM through DataContext (easy way). Your VM can update the ObservableCollection as appropriate, and the view will get the changes through the usual binding.
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.