XAML Data Binding based on Button Click - wpf

I have, I think a simple question. I have several buttons and depending on which one is clicked, need to bind data to my DataGrid - for examples if Button1 is clicked, bind fields A-D to the grid; if Button2 is clicked, bind fields E-J to the grid. I have the data binding working fine but can't seem to integrate the buttons to determine which data to bind. The same DataGrid is using no matter which button is pressing but I need to bind different data based on which button is clicked. Any thoughts?

Use ToggleButton instead of Button, as they expose IsChecked property.
Declare a Freezable like <DiscreteObjectKeyFrame x:Key="A-D" Value="True"/> under Window.Resources or DataGrid.Resources .
Define <BooleanToVisibilityConverter x:Key="BooleanToVisCnvKey"/> under Window.Resources or DataGrid.Resources.
Bind Visibility of DataGridColumn to DiscreteObjectKeyFrame .Value declared in (2) above, and use a IValueConverter to convert boolean to Visibility.
<Window.Resources>
<DiscreteObjectKeyFrame x:Key="FlagKey" Value="False"/>
<BooleanToVisibilityConverter x:Key="BooleanToVisCnvKey"/>
</Window.Resources>
...
<DataGrid>
...
<DataGridTextColumn Visibility="{Binding Value, Source={StaticResource FlagKey}, Converter={StaticResource BooleanToVisCnvKey}}" ...>
...
</DataGrid>
...
<ToggleButton ... IsChecked="{Binding Value,Source={StaticResource FlagKey}, Mode=TwoWay}" />

Related

How to bind PreviewMouseDown to FormattedText within an ItemsControl

In WPF using MVVM I would like to set a property in the view model to the displayed text when the mouse is clicked. That is I want the PreviewMouseDown event from the ItemsControl to set a property in the viewmodel.
In the following XAML, I am using an ItemsControl to display Strings from a FormattedText ObservableCollection. All goes well with the XAML below to display the FormattedText.
But, how can I bind a PreviewMouseDown to each of the generated items for the view model?
All my attempts to use DataTemplate within the ItemsControl ultimately lead to:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type;
XAML
<ItemsControl
ItemsSource="{Binding Strings}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
Background="Transparent"
Width="{x:Static h:Constants.widthCanvas}"
Height="{x:Static h:Constants.heightCanvas}"
/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Adding
h:MouseBehaviour.PreviewMouseDownCommand="{Binding PreviewMouseDown}"
to the Canvas definition never results in the command being called and I can't add it in a DataTemplate.
Any help or better idea is appreciated.
as items in an ItemsControl are hosted in ContentPresenter so if you bind your command to the same it will be applied to the Item's in the ItemsControl
so for that purpose we can use a generic Style for ContentPresenter in the resources of ItemsControl or any parent container
eg
<Style TargetType="ContentPresenter">
<Setter Property="h:MouseBehaviour.PreviewMouseDownCommand"
Value="{Binding PreviewMouseDown}" />
</Style>
above example is based on assumption that PreviewMouseDown command is in the view model for each item, if the command is in the parent view model then you may perhaps use
<Style TargetType="ContentPresenter">
<Setter Property="h:MouseBehaviour.PreviewMouseDownCommand"
Value="{Binding PreviewMouseDown, RelativeSource={RelativeSource FindAncestor,AncestorType=ItemsControl}}" />
</Style>

How to bind to Tabcontrol.Items

I have a WPF application that I'm trying to dynamically add items to a tabcontrol. I have a list of menu items that should be databound to the tabcontrol's items. The only problem is that TabControl.Items does not notify others that items have been added. I've tested this by binding instead to TabControl.Items.Count and get calls to the converter (but the value passed in is the count and not something useful). Here's the relevent code that doesn't get databound properly because Items doesn't call out updates:
<MenuItem ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items, Converter={StaticResource TabControlItemConverter}}">
This MenuItem XAML is inside a ControlTemplate for a TabControl. With static items, i.e., items that are already defined in a TabControl, this code works perfectly. But I have a TabControl that gets items added at runtime and can't seem to update this binding. Has anyone added some sort of attached property to a TabControl that can bind to the Items collection?
Edit for background info
The TabControl that has items added to it is a region (this is a Prism application). Here is the relevent XAML
<TabControl cal:RegionManager.RegionName="{x:Static local:LocalRegionNames.SelectedItemRegion}" >
<TabControl.Resources>
<Style TargetType="TabItem" BasedOn="{StaticResource TabItemStyle}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Style="{StaticResource tabItemImage}" Height="20" />
<TextBlock Text="{Binding Content.DataContext.TabHeader, RelativeSource={RelativeSource AncestorType=TabItem}}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The relevent code for adding a view to the region is here:
ProjectDetailView view = new ProjectDetailView();
ProjectDetailViewModel viewModel = new ProjectDetailViewModel();
viewModel.CurrentProject = project;
view.DataContext = viewModel;
IRegionManager retManager = RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Add(view, null, true);
RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Activate(view);
All this works fine...views get added, the tab control adds items, and views appear. But the Items property on the tabcontrol never broadcasts the changes to its collection.
You do the same thing for TabControls, you bind the ItemsSource, the only thing you need to take into account is that the source collection should implement INotifyCollectionChanged if you want it updated if items are added. ObservableCollection<T> already implements the interface and is often used as source for such bindings.

How do I apply a Style Setter for ListBoxItems of a certain DataType?

My WPF ListBox contains two types of object: Product and Brand.
I want my Products selectable. I want my Brands not selectable.
Each of the two types has its own DataTemplate.
By default, anything may be selected:
<ListBox ... >
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Product}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type local:Brand}">
...
</DataTemplate>
</ListBox.Resources>
</ListBox>
I can set Focusable with a Setter, but then nothing may be selected:
<ListBox ... >
<ListBox.Resources>
...
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False" />
</Style>
</ListBox.Resources>
</ListBox>
I cannot put the Setter within the DataTemplate.
I cannot put a DataType onto the Style.
How do I style only the ListBoxItems of type Brand?
Thanks to the StyleSelector class you can attach styles depending on type of data for the ItemContainerStyle. There is a really good example here : http://www.telerik.com/help/wpf/common-data-binding-style-selectors.html
Can you use a data trigger on your ListBoxItem style? If so, bind to the DataContext (your class) and use a value converter to test the type. If it's the one you're interested in, style the ListBoxItem so that it cannot appear selected.
I don't think you can disallow selection of an item in a Selector (parent of ListBox) without codebehind or a custom Behavior.

Run code after the animation is finished

I am using MVVM Light. I have created a window that looks like this:
<Window Name="MainWindow" ...>
<Window.Resources>
...
<viewModels:MainViewModel x:Key="mainVM" />
...
<BooleanToVisibilityConverter x:Key="visConv" />
...
</Window.Resources>
<Grid DataContext="{StaticResource mainVM}>
...
<Button Command="{Binding RaiseMyControl}" />
...
<my:MyUserControl Visibility="{Binding MyControlVisible,
Converter={StaticResource visConv}}" />
</Grid>
</Window>
So basically, the MainViewModel is a view model class for the window. It contains:
bool MyControlVisible property which is binded to MyUserControl's Visibility
property
RelayCommand RaiseMyControl command which purpose is to set the value of the
MyControlVisible property to true (default is false).
Clicking the button in the window results in the appearance of the MyUserControl - simple.
MyUserControl user control looks like this:
<UserControl ...>
<UserControl.Resources>
...
<viewModels:MyUserControlViewModel x:Key="userControlVM" />
...
</UserControl.Resources>
<Grid DataContext="{StaticResource userControlVM}>
...
<Border Width="200" Height="100" Background="Red">
<TextBlock Text="{Binding MyUserControlText}" />
</Border>
<!-- This border has a DataTrigger bound to "bool Fading" property of
the view model. When Fading is true, the border fades in through
an animation. When it is false, the border fades out. -->
...
<Button Command="{Binding CloseMyControl}" />
</Grid>
</UserControl>
Again, very simple. The MyUserControlViewModel is a view model class for the user control. It contains:
string MyUserControlText property which is binded to TextBlock's Text
property
bool Fading property which is binded to border's data template, and is used to make
the border fade in or out
RelayCommand CloseMyControl command which does two things: 1. It sets the Fading
property to false to make the border fade out, and 2. it sets the Visibility
property of the user control to Collapsed.
Here's the problem: as soon as the Visibility is set to Collapsed, the user control disappears. I need it to fade out first and then to disappear afterwards. How can I make it happen? Thanks.
Since the visibility belongs to the fade-out i would run two animations at the same time. Additionally to your fade-animation (of whatever type or composite that may be) you can create a ObjectAnimationUsingKeyFrames which sets the Visibiliy at the key time at which the fade ends.
XAML example:
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
Additionally all storyboards and animations have a Completed event to which you could subscribe and just set the value right away.
To direct the animation at another control use Storyboard.Target for complex references, or Storyboard.TargetName for reference by name.
To animate the UserControl you could try:
Storyboard.Target="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
or
Storyboard.Target="{Binding RelativeSource={RelativeSource AncestorType=my:MyUserControl}}"
Both should work if the logical tree is intact.
I'd try setting the visibility to Collapsed as part of the fade out animation, not a separate line in the CloseMyControl command.

WPF DataGrid - Hiding Column using a CheckBox

I am trying to control the visibility of a column using a checkbox (this is in WPF 4.0).
Here is a snippet of my XAML:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Window.Resources>
<CheckBox x:Name="GeneralDetailsVisible" Content="General Details" Margin="5"/>
<DataGridTextColumn Header="Crew"
Binding="{Binding Path=Crew}"
Visibility="{Binding ElementName=GeneralDetailsVisible,
Converter={StaticResource BoolToVisConverter},
Path=IsChecked}">
</DataGridTextColumn>
Now I know the BooleanToVisibilityConverter converter is working as I bound it to a text block and I can see the values I am expecting. If I enter the values by hand into the columns visibility property it works. But not when I bind it. What am I doing wrong?
Answer:
Quartermeister pointed me to the answer. The page he pointed to is a bit misleading as the code listed on the post does not work and you must look at the sample code.
Here is my final, working code, for anyone else who runs into this problem:
Converter to turn our ViewModels bool property into the correct Visibility value for our column attribute.
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Window.Resources>
Bind the checkbox to the ViewModels property that will control the columns visibiity.
<CheckBox
x:Name="DetailsVisible"
Content="Show Details"
IsChecked="{Binding Path=DisplayDetails}" />
Then bind Visibility to the ViewModels DisplayDetails property. Notice that it is the columns own DataContext that is bound to.
<DataGridTextColumn
Header="Reliability"
Binding="{Binding Path=Reliability}"
Visibility="{Binding (FrameworkElement.DataContext).DisplayDetails,
RelativeSource={x:Static RelativeSource.Self},
Converter={StaticResource BoolToVisConverter}}">
</DataGridTextColumn>
Add the following code to your project, this allows the catching of the change in a DataGrids DataContext.
FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn));
FrameworkElement.DataContextProperty.OverrideMetadata(typeof(DataGrid),
new FrameworkPropertyMetadata
(null, FrameworkPropertyMetadataOptions.Inherits,
new PropertyChangedCallback(OnDataContextChanged)));
Then whenever your DataGrids DataContext is changed we update all the attached DataGridColumsn with the new DataContext.
public static void OnDataContextChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
DataGrid grid = d as DataGrid;
if (grid != null)
{
foreach (DataGridColumn col in grid.Columns)
{
col.SetValue(FrameworkElement.DataContextProperty, e.NewValue);
}
}
}
One Gotcha to look out for. If you add your DataContext to your page like so:
<Window.DataContext>
<vm:WeaponListViewModel />
</Window.DataContext>
Then the above function will be called before your DataGrid has any columns!
I got around this by manually attaching my DataConext in code behind after the window was created.
IMO using x:Name, x:Reference and Source is simpler:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<CheckBox x:Name="showImperial" Content="Show Details" />
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Header="TOV (Bls)"
Width="80"
Binding="{Binding TOVBarrels}"
Visibility="{Binding Source={x:Reference showImperial},
Path=IsChecked,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
</DataGrid.Columns>
</DataGrid>
See this answer:
Bind visibility to checkable menu item shows error "Service provider is missing the INameResolver service" in WPF
The problem is that the DataGrid columns are not part of the visual tree, so they cannot use bindings. (The "Binding" property is actually a normal property of type Binding, not a dependency property. It applies that binding object to the cells, which are part of the visual tree.)
Here is a link to a blog that has a workaround and demonstrates binding the visibility of a column to a check box: Link

Resources