Setting AutomationProperties on templated control - c#

I'm needing be able to set an AutomationId on some controls that are generated from a template based on assets that are checked into a system.
I've verified that all of the custom controls that encapsulate the templated controls handle AutomationId properties.
I've tried the following with no luck:
<DataTemplate DataType="assets:IAssetVM">
<Border Background="Transparent" x:Name="ItemContent" MouseLeftButtonUp="ItemContent_OnMouseLeftButtonUp">
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding FollowAssetButtonCommand}" />
</Border.InputBindings>
<Grid Height="30">
<AutomationProperties.AutomationId>
<MultiBinding StringFormat="AID_{0}-{1}">
<Binding Path="Name" />
<Binding Path="NumericID" />
</MultiBinding>
</AutomationProperties.AutomationId>
...
</Grid>
</Border>
</DataTemplate>
Update
Looking back through the logical tree of the application while it's running I realized that the last level I was able to get to with the automation properties assignment contains a couple of different GroupItem classes (different command modes for the assets in the system).
So the structure looks like:
-> List View "" "Test_AssetListView"
|-> Group Item "Mode 1" ""
|-> List Item "Vm.Assets.AssetVM2" ""
|-> Group Item "Mode 2" ""
I don't think I should need to override the OnCreateAutomationPeer() method for the Group control as (I'm pretty sure) it's a base(?) type of control.
Any ideas?
Thanks in advance for any help!

Related

WPF Datagrid ComboBox options different based on another row value

I have a datagrid where the ItemsSource is set in code-behind for example:
var grid = grdEmploy as DataGrid;
grid.ItemsSource = employments; // list of objects
In this grid, I have several drop downs being used when editing the row. The options are currently held in a local CollectionViewSource, for example:
<CollectionViewSource x:Key="StatusList" CollectionViewType="ListCollectionView"/>
And set when the window is loaded like so:
var statusList= Functions.GetStatuses(); // returns a List<>
CollectionViewSource itemCollectionViewSource;
itemCollectionViewSource = (CollectionViewSource)(FindResource("StatusList"));
itemCollectionViewSource.Source = statusList;
Then the binding of the column for the grid would look like so:
<DataGridTemplateColumn Header="Employment Status" HeaderStyle="{StaticResource WrappedColumnHeaderStyle}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Converter>
<local:AimTypeConverter />
</MultiBinding.Converter>
<Binding Path="EmpStat" />
<Binding Path="SourceCollection" Source="{StaticResource StatusList}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedValue="{Binding EmpStat}" SelectedValuePath="Value" DisplayMemberPath="Text" ItemsSource="{Binding Source={StaticResource StatusList}}"></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
This all works great however I have hit a snag whereby one of the columns needs to show different options based on another column. For example if Column A is "1" show Options 2,3, if "2", show Options 3,4 etc.
My thoughts were to load all options into the local list and somehow filter them but I'm not sure how best to do this, any help on this would be appreciated.
The way to solve this using the MVVM pattern would be to define a collection property in the Employee class, or whatever you call it, and then return an already filtered collection from this property based on the value of the property bound to "Column A".
I am afraid it doesn't make much sense to define a single source collection in the code-behind if you want to bind to several source collections, filtered or not, in the DataGrid. I would recommend you to put your filtering logic in the view model.

Reusable DataTemplate (in Resources) using a convertor that needs a parameter

I have to display in a grid 12 columns (one for each months) with different data.
In order to correctly display the data, I use DataTemplates for every column and multibindings. The multibindings specify a converter which is parametrized with the index of the month.
<DataTemplate x:Key="CustomerForecastQuantityDataTemplate">
<TextBlock TextAlignment="Right">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ForecastCustomerQuantityConverter}" **ConverterParameter="0"**>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type view:BaseView}}" Path="DataContext.ForecastPeriods"/>
<Binding Path="ForecastQuantities"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
I am trying to make the xaml look a bit more elegant, and have one DataTemplate defined as a static resource, and use it everywhere, like this:
<forecast:ForecastCustomerMonthQuantityGridViewDataColumn
Header="{Binding RelativeSource={RelativeSource Self}, Path=MonthIndex}"
HeaderTextAlignment="Center"
HeaderTextWrapping="Wrap"
Width="60"
IsReadOnly="True"
MonthIndex="1"
CellTemplate="{StaticResource CustomerForecastQuantityDataTemplate}"/>
My question is, how can I make this DataTemplate take a diferrent parameter depending on the MonthIndex value of each ForecastCustomerMonthQuantityGridViewDataColumn
Thanks a lot, every suggestion is highly appreciated (it might be that I don't have a good understanding of the DataTemplate concept)
I can't think of a XAML-only way to get at "MonthIndex" from within the cell template (and on a side note ConverterParamter doesn't support binding). Off the top of my head, you could try something like this (I'm away from my PC so not able to try it myself):-
Add a third binding to your multi value converter, bound to the cell being templated, something like:
<binding RelativeSource="{RelativeSource AncestorType={x:Type DataGridCell}}" />
Your multi value converter now has access to the cell, which in turn exposes a Column property, which you can cast to ForecastCustomerMonthQuantityGridViewDataColumn to get at "MonthIndex".

WPF Button IsEnabled Check on Multiple Bindings

I have the IsEnabled property of a XAML button configured with the following databinding currently:
<Button Name="ThirdPartyPostoneButton" Content="Postpone"
Click ="postponeThirdPartyUpdatesButton_Click" Margin="5,5,0,0"
Height="25" IsEnabled="{Binding Item3.CanDefer}"/>
I need to also add a check for IsEnabled="{Binding Item3.InstallSourceExists}" (in other words both criteria must be met in order for the button to be enabled). How can I accomplish this?
Two options I can think of:-
Use a MultiBinding plus a custom IMultiValueConverter that checks both values are true.
Expose a new property on your "Item3" model that simply returns true if the other properties are both True. This is a cleaner approach, and means that if the logic changes in the future (e.g. you need to include a third bool property), you don't have to touch your XAML.
Use a multibinding like this:
<Button Name="ThirdPartyPostoneButton" Content="Postpone" Click ="postponeThirdPartyUpdatesButton_Click" Margin="5,5,0,0" Height="25" >
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource MyCustomConvertor}">
<Binding Path="Item3.CanDefer"/>
<Binding Path="Item3.InstallSourceExists"/>
</MultiBinding>
</Button.IsEnabled>
</Button>

Why does this ComboBox ignore the DataTemplate when SelectedItem is a ContentControl?

In our application we have a screen design feature which is comprised of a custom ScreenDesignPanel and a Property Grid with a ComboBox at the top which points to the selected item on the ScreenDesignPanel. This allows the user to select the UIElement via the ComboBox or via the mouse to set its properties. We achieve this by binding the ItemsSource of the ComboBox to the ScreenDesignPanel's Children collection, then binding their SelectedItems together. This works great.
However, for whatever reason, if the SelectedItem is a ContentControl or a subclass like Button the ItemTemplate specified for the ComboBox is ignored for the 'selected item area' but it is applied when displaying the item in the dropdown list. If the SelectedItem is not a ContentControl, the template is used in both cases.
This also is seemingly specific to the ComboBox. If we use any other selector control: ListBox, ListView, ItemsControl... even third-party ComboBox controls... they all work as expected, properly applying the DataTemplate. ComboBox is doing something internally which no other control is doing.
Note: Below is an over-simplified example for illustrative purposes of the issue only. It is not how we're actually using it as described above.
Also of note: In the DataTemplate for the ComboBox.ItemTemplate, we are only using properties (i.e. Foreground in the example), and are not displaying the DataContext (i.e. the actual ContentControl) itself. This is important because again, the actual control already exists on the ScreenDesignPanel and therefore can't be used for display in the ComboBox's ItemTemplate as it would have two parents which isn't allowed. In other words, it is being used purely as data here.
One last thing... we have a working solution in our app, which was to wrap the Children before binding it to the ComboBox.ItemsSource. However, I'm still curious as to why the ComboBox behaves the way it does which is SPECIFICALLY what I'm asking. (In other words, I'm not looking for other solutions to this design. We already have a working one. I'm looking for clarity on the odd behavior of the ComboBox itself.)
On to the code!
In the first example below, note how the data template is applied to everything in the dropdown, but the selected item area only uses a template if the selected item is not a ContentControl.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="I am the template" Foreground="{Binding Foreground}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<!-- Four 'Data' items for example only -->
<TextBlock Text="I am a Red TextBox" Foreground="Red"/>
<ListBox Foreground="Purple">
<ListBoxItem>I am a Purple ListBox</ListBoxItem>
</ListBox>
<ContentControl Content="I am a Blue ContentControl" Foreground="Blue" />
<Button Content="I am a Button with Green text" Foreground="Green" />
</ComboBox>
This second example shows that it is completely acceptable and fully supported to use a UIElement as the content of a ContentPresenter and still use a DataTemplate (via ContentTemplate) so you can use it in a purely-data role, allowing the template itself to define the visual appearance without displaying the UIElement itself, which is used purely as data here.
<ContentPresenter>
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock Text="I am the ContentTemplate" Foreground="{Binding Foreground}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.Content>
<Button Content="I am the button" Foreground="Green" />
</ContentPresenter.Content>
</ContentPresenter>
Again, the issue is specific to a ComboBox. I want to find out why the data template isn't applied in that single case, and how to force it to be applied, if possible.
Of note, ComboBox does define SelectionBoxItemTemplate which is separate from the regular ItemTemplate but the rub is that is read-only so you can't set it. We really don't want to re-template the ComboBox as that can mess up proper theming.
Have you tried explicitly setting the DataTemplate to the ContentControl.ContentTemplate property?:
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate">
<TextBlock Text="{Binding Content,
StringFormat='Displayed via template: {0}'}" />
</DataTemplate>
</UserControl.Resources>
...
<ContentControl Content="ContentControl"
ContentTemplate="{StaticResource DataTemplate}" />

Unset/Change Binding in WPF

How can I unset the binding applied to an object so that I can apply another binding to it from a different location?
Suppose I have two data templates binded to the same object reference.
Data Template #1 is the default template to be loaded. I try to bind a button command to a Function1 from my DataContext class:
<Button Content="Button 1" CommandParameter="{Binding }" Command="{Binding DataContext.Function1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
This actually works and the function gets binded. However, when I try to load Data Template # 2 to the same object (while trying to bind another button command to a different function (Function2) from my DataContext class):
<Button Content="Button 2" CommandParameter="{Binding }" Command="{Binding DataContext.Function2, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
It doesn't work and the first binding is still the one executed. Is there a workaround to this?
EDIT (for better problem context):
I defined my templates in my Window.Resources:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<local:View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<local:View2 />
</DataTemplate>
</Window.Resources>
The View1.xaml and the View2.xaml contain the button definitions that I described above (I want them to command the control of my process flow).
ViewModel1 and ViewModel2 are my ViewModels that implement the interface IPageViewModel which is the type of my variable CurrentPageViewModel.
In my XAML, I binded ContentControl to the variable CurrentPageViewModel:
<ContentControl Content="{Binding CurrentPageViewModel}" HorizontalAlignment="Center"/>
In my .CS, I have a list defined as List<IPageViewModel> PageViewModels, which I use to contain the instances of my two View Models:
PageViewModels.Add(new ViewModel1());
PageViewModels.Add(new ViewModel2());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
When I try to change my CurrentPageViewModel to the other view model, this is when I want the new binding to work. Unfortunately, it doesn't. Am I doing things the right way?
If for some reason you are unable to use just two different DataTemplates, usually because the datatemplates are very large or complex, i suggest using ContentControl and DataTemplateSelector.
In your DataTemplates place another ContentControl, create 2 DataTemplates just containing your button, one with Function1 one with Function2. Create a DataTemplateSelector and set it on the initial ContentControl. The DataTemplateSelector now just need to select the proper template depending on a decision, for example the type of the item or a property on the item etc.
If you still want to unset binding you can do it from code like:
BindingOperations.ClearBinding(txtName, TextBox.TextProperty)
But TemplateSelector approach will be more efficient.

Categories