Access another control in a DataTemplate in code behind - wpf

I have a DataTemplate containing multiple controls. One of the controls is a button that needs to access the other controls in the datatemplate
<DataTemplate>
<StackPanel>
<ComboBox x:Name="optionsCombo" >
<ComboBoxItem Content="Option1" />
<ComboBoxItem Content="Option2" />
<ComboBoxItem Content="Option3" />
</ComboBox>
<Button Name="DoSomethingButton" Margin="10" Click="DoSomethingButton_Click">Do Something</Button>
</StackPanel>
</DataTemplate>
In the code behind for the button click event, if I attempt to access the ComboBox by name like this:
private void DoSomethingButton_Click(object sender, RoutedEventArgs e)
{
ComboBoxItem myItem = (ComboBoxItem)optionsCombo.SelectedItem;
}
I get an error: "The name 'optionsCombo' does not exist in the current context"
So, how do I access the other controls in the DataTemplate from the button click event?

You can't access it like that, because there's no code generation for DataTemplates, i.e. the optionsCombo ComboBox does not really exist in the compilation time, hence the error. To manipulate it in the code behind, you need to use VisualTreeHelper, it's well described over the net. Just get the parent Panel of the sender, then find your ComboBox by name, and then cast it to it's proper type. There you have it!

Related

DataGridColumnHeader of a DataGrid defined in a DataTemplate for a ViewModel

I have successfully used ProxyElement to pass Data Context of my Data Grid to DataGridColumnHeaders. However, I am trying out something new and I just can't figure out what I am doing wrong over here.
Here is what I am trying to do: I am creating a UserControl and associating it to my ViewModel in my Resources file (see Resources.xaml code snippet below).
Resources.xaml:
<ResourceDictionary
xmlns:myVm="clr-namespace:..."
xmlns:myUserControl="clr-namespace:...">
<DataTemplate DataType={x:Type myVm:DummyModel}">
<myUserControl:DummyUserControl />
</DataTemplate>
</ResourceDictionary>
Now in my UserControl, I have a DataGrid with a DataGridComboBoxColumn. I am trying to access my data context to set its item source and in the past I was able to do it using proxy element. This time however I am not able to do so. (See DummyUserControl.xaml code snippet below)
DummyUserControl.xaml:
<UserControl x:Class="Client.MyControl.DummyUserControl"
...>
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" x:Name="ProxyElement"
DataContext="{Binding}" />
</UserControl.Resources>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridComboBoxColumn
Header="Company"
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticResource ProxyElement}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataGrid>
</UserControl>
When I do this, my binding fails with the following message:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
BindingExpression:(no path); DataItem=null; target element is 'FrameworkElement'
(Name='ProxyElement'); target property is 'DataContext' (type 'Object')
I have no idea what to do here. I remember reading that the datacontext for a datatemplate is automatically set, so I have no idea why the data context is null in this case. To prove it is null, I also tried setting the binding in the code-behind file and added a breakpoint to check its value (which was null).
Can anyone suggest what to do here?
Edit 1
I have also tried the following approaches:
Remove ProxyElement altogether and see if it can detect DataContext. To no surprise, this failed.
Tried binding to the templated parent. Fail.
Tried binding to the UserControl itself. Fail.
I also tried referencing the data context of the parent item where this view model is going to be displayed, which is in a TabItem of a TabControl.
All of the alternate bindings gave me same error as the error above.
Here is a (working but not preferred) solution to this problem. You will realize why it is not preferred by the end of it.
The key to this problem is understanding what and how data context of a data template works. Whenever you define a Data Template for a View Model, the data context for the view that follows, whether it is a user control or just xaml itself, is the View Model! This shouldn't be a surprise to anyone.
But this will surprise people: if you specify a User Control, the Data Context of the User Control is not set during construction of the User Control! In other words, in the constructor of User Control, Data Context is going to be null. Furthermore, any XAML code that relies on the Data Context at construction time, which in this case was my FrameworkElement resource called ProxyElement got its DataContext set to null because it gets constructed at construction time of the User Control!
So when does the DataContext get set? Simply after the User Control is created. In pseudo code, this following describes the logic behind drawing a ViewModel:
Draw ViewModel x;
DataTemplate in ResourceDictionary says ViewModel x can be drawn using UserControl abc
Let's create a new instance of UserControl abc
Let's now assign the DataContext of abc to the ViewModel itself.
Let's return the newly created instance of UserControl abc
So what do we do to solve the problem in this question?
UserControl.xaml:
<UserControl ...
DataContextChanged="DaCoHasChanged">
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" /> <!--Remove DataContext="{Binding}"-->
</UserControl.Resources>
<DataGrid ...>
<DataGridComboBoxColumn
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticSource ProxyElement}}"
... />
</DataGrid>
</UserControl>
UserControl.xaml.cs:
private void DaCoHasChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var proxyElement = Resources["ProxyElement"] as FrameworkElement;
proxyElement.DataContext = e.NewValue; // instead of e.NewValue, you could
// also say this.DataContext
}
I am trying to figure out a way of getting rid of the code in the code-behind file. But till then, if someone else hits this problem, then they might be able to get inspired from this solution.
Credit to the concept behind this solution goes to: How to set the DataContext for a View created in DataTemplate from ViewModel
Try this
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridTemplateColumn Header="Company">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path="{Binding DataContext.ProductCompanies,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Now as I got your problem I think the problem is in DataGridComboBoxColumn I dont know why it not Binding using RelativeResource . Try it with DataGridTemplateColumn and you would not require any ProxyElement I hope this will help.

WPF - How to restyle ComboBox to remove the textbox/editbox and replace with static text

I want to restyle a WPF ComboBox which is formatted to be a drop-list type, BUT remove the selected TextBox which gets populated with the selected contents and just replace it with some static text and an image which remains constant, simulating a button like look.
So in effect it becomes a button-drop-list, so when I select an item from the drop list, I can populate another control via command bindings with its selected value and the button style remains.
Basically something like this crude picture I've hacked together.
I've seen examples of button with context menus, but I don't like the idea, and a ComboBox fits my needs perfectly in terms of function and easy command and data binding.
I know it can be done, but I lost faith in my ablity after reading overly confusing examples based on other controls. I couldn't find an example detailing my needs to learn from.
Cheers
DIGGIDY
After much playing around, I decided the better option was to go for a button with bound context menu, this worked out to be the better solution in the end.
Thanks for your help Marc.
I had got the same problem and actually, it's simple.
Just put a read-only ComboBox with a SelectionChanged event.
You put in index 0 your static text.
Now, when the user is selecting something, get the selected item and then, set the SelectedIndex to 0. So you got the item the user selected but the displayed text is the same.
See:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox combo = (ComboBox)sender;
if (combo.SelectedIndex > 0)
{
// Do your stuff here...
// Then
combo.SelectedIndex = 0;
}
}
[EDIT] According to me, I prefer my previous answer. So make sure you, reader, that my previous answer doesn't match your expectations. [/EDIT]
Another answer is to put your object above the ComboBox and then catch the MouseDown event from this object and dropped down the ComboBox. I used a read-only TextBox in my example.
See:
<Grid>
<ComboBox x:Name="Combo" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="120">
<ComboBoxItem Content="TEST" />
<ComboBoxItem Content="TEST1" />
<ComboBoxItem Content="TEST2" />
<ComboBoxItem Content="TEST3" />
</ComboBox>
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" Text="TextBox" VerticalAlignment="Top" Width="120" IsReadOnly="True" PreviewMouseDown="TextBox_PreviewMouseDown"/>
</Grid>
And then the code behind:
private void TextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true; // Prevents the event.
Combo.IsDropDownOpen = true; // Drops down the ComboBox.
}
It works fine for me.

How can I perform a custom action on the data of a DataGridRow when it is selected?

I've been trying to figure out how to get this custom behaviour into a datagrid with out having much look when searching online for solutions.
Given the following datagrid (some xaml removed for brevity):
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="auto">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have the checkbox successfully bound to the databound object for each row. (Note: I'm using a DataGridTemplateColumn rather than DataGridCheckBoxColumn so that you do not need to double-click to change the value).
What I would like to achieve is to have the ability to tick the checkbox / update the Selected property of the databound object when the user selects a row. Effectively making the entire row click set the checked property of the checkbox. Ideally, I'd like to do this without a code behind file if possible as I'm trying to keep my code behinds as clean as possible.
Another feature which I would like, if possible, would be that clicking on a row would toggle it's selected property so that if you click on another one, the previous one stays selected as well as the new one.
Any help is much appreciated.
For clarity. I understood
Another feature which I would like, if possible, would be that
clicking on a row would toggle it's selected property so that if you
click on another one, the previous one stays selected as well as the
new one.
in the way, that you want the CheckBox of the an item, respectively the Selected property on the items ViewModel, to stay selected, when the next DataGridRow is selected, but not the DataGridRow itself? Is that correct?
My suggestion is to extend the behavior of your DataGrid using *WPF behavior*s (This is a good introduction. This way you can keep your codebehind clear, but don't have to twist XAML to make it do what you want.
This is basically the idea of behaviors: Writing testable code, which is not coupled to your concrete view, but nonetheless allowing you to write complicated stuff in 'real' code and not in XAML. In my opinion your case is a typical task for behaviors.
Your behavior could look about as simple as this.
public class CustomSelectionBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
// Set mode to single to be able to handle the cklicked item alone
AssociatedObject.SelectionMode = DataGridSelectionMode.Single;
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
// Get DataContext of selected row
var item = args.AddedItems.OfType<ItemViewModel>();
// Toggle Selected property
item.Selected = !item.Selected;
}
}
Attaching the behavior to your specific DataGrid, is done in XAML:
<DataGrid ...>
<i:Interaction.Behaviors>
<b:CustomSelectionBehavior />
</i:Interaction.Behaviors>
...
</DataGrid>
You need to reference
System.Windows.Interactivity.dll
which contains the Behavior<T> baseclass as well.

WPF Treeview tooltip - dynamic

This is regards to MVVM & WPF -showing dynamic tooltip based on mouse location on a WPF Treeviewitems. Let say when the user hovers over a node which has got some business data based on that it should display tooltip.
Example: let say If i hover over manager items..should say -"locked" and for others "Ready to edit"..etc. Depends on the mouse over items data..tooltip text should guide the user to do further action. I'm trying to do some VM setting of tooltip text with the help of Tooltip opening event of TreeViewitem and trying to update the tooltip text..but facing some issue.
What are all the best and simple way to do it. Tried behavior and end up with some error.
System.Windows.Markup.XamlParseException occurred
Message="Cannot add content of type 'x.x.Views.CustomTreeView.TreeTooltipBehavior' to an object of type 'System.Windows.Interactivity.BehaviorCollection'. Error at object 'x.x.x.Views.CustomTreeView.TreeTooltipBehavior' in markup file 'x.x.x.Views;component/mypage.xaml' Line 72 Position 53."
Source="PresentationFramework"
Code:
<MyMyControls:ExtendedTreeView x:Name="MyTreeView" ItemsSource="{Binding MyDriveCollection}"
ItemContainerStyle="{StaticResource TVStyleTemplate}">
<MyMyControls:ExtendedTreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type NavModel:TreeDataItem}" ItemsSource="{Binding MyDriveCollection}">
<MyControls:SimpleEditableTextBlock x:Name="TabLabel" Text="{Binding Path=MenuText, Mode=TwoWay}"
ToolTip="{Binding MenuText,Mode=TwoWay}">
</MyControls:SimpleEditableTextBlock>
</HierarchicalDataTemplate>
</MyControls:ExtendedTreeView.ItemTemplate>
<MyControls:ExtendedTreeView.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding MenuText}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type TreeView}},Path=SelectedItem}" Command="{Binding Command}">
</MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</MyControls:ExtendedTreeView.ContextMenu>
<i:Interaction.Behaviors>
<CustomTreeView:TreeTooltipBehavior CustomTreeView:ToolTipOpeningCommand="{Binding ToolTipOpeningCommand,Mode=TwoWay,diag:PresentationTraceSources.TraceLevel=High}" />
<CustomTreeView:WorkspaceTreeViewContextMenuBehavior ContextMenuOpeningCommand="{Binding ContextMenuOpeningCommand}"/>
</i:Interaction.Behaviors>
</MyControls:ExtendedTreeView>
TreeTooltipBehavior.cs
public class TreeTooltipBehavior : Behavior<ExtendedTreeViewItem>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.ToolTipOpening += new ToolTipEventHandler(AssociatedObject_ToolTipOpening);
}
void AssociatedObject_ToolTipOpening(object sender, ToolTipEventArgs e)
{
if (sender != null)
{
TreeDataItem hit = ((TreeDataItem) (((FrameworkElement) (sender)).DataContext));
if (ToolTipOpeningCommand != null)
{
ToolTipOpeningCommand.Execute(hit);
}
}
}
public static readonly DependencyProperty ToolTipOpeningCommandProperty = DependencyProperty.Register(
"ToolTipOpeningCommand",
typeof(ICommand),
typeof(TreeTooltipBehavior),
new PropertyMetadata(null));
public ICommand ToolTipOpeningCommand
{
get { return (ICommand)GetValue(ToolTipOpeningCommandProperty); }
set { SetValue(ToolTipOpeningCommandProperty, value); }
}
}
In my view model I'm expecting to handle the ToolTipOpeningCommand and declared enough to get the event via Behavior class.
interestingly the contextmenu behavior works fine but tooltip behavior throws xaml parser exception..
1) Am i defined at the right place (for behavior) ?
2) If Contextmenu behavior works then why not tooltipbehavior ?
3) Any clue from the exception pasted at the top ?
I'm expecting to,
(Xaml)-tooltip behavior -> to invoke tooltipopening event in the (behavior class) -> which in turns invoke the command defined in the ViewModel. I tried this similar way for context menu and works fine.
Pls provide some hint about fixing this issue.
The XAML parser error is because you are trying to attach a Behavior that only applies to ExtendedTreeViewItem to an ExtendedTreeView element. In other words, if you change Behavior<ExtendedTreeViewItem> to Behavior<ExtendedTreeView>, you will fix the parse error.
Although we cannot see from the code you presented, the reason the ContextMenu works is probably because WorkspaceTreeViewContextMenuBehavior derives from a Behavior with a generic type parameter that is compatible with ExtendedTreeView, such as FrameworkElement.

Setting CommandTarget to selected control in a TabControl

I have a WPF window with a few buttons and a tabcontrol having a tab for each 'document' the user is working on. The tabcontrol uses a DataTemplate to render the data in ItemSource of the tabcontrol.
The question: If one of the buttons is clicked, the command should be executed on the control rendering the document in the active tab, but I've no idea what I should set CommandTarget to. I tried {Binding ElementName=nameOfControlInDataTemplate} but that obviously doesn't work.
I tried to make my problem a bit more abstract with the following code (no ItemSource and Document objects, but the idea is still the same).
<Button Command="ApplicationCommands.Save" CommandTarget="{Binding ElementName=nestedControl}">Save</Button>
<TabControl x:Name="tabControl">
<TabControl.Items>
<TabItem Header="Header1">Item 1</TabItem>
<TabItem Header="Header2">Item 2</TabItem>
<TabItem Header="Header3">Item 3</TabItem>
</TabControl.Items>
<TabControl.ContentTemplate>
<DataTemplate>
<CommandTest:NestedControl Name="nestedControl"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I tested the code by replacing the complete tabcontrol with only one single NestedControl, and then the command button just works.
To be complete, here is the code of NestedControl:
<UserControl x:Class="CommandTest.NestedControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Label x:Name="label" Content="Not saved"/>
</Grid>
</UserControl>
And code behind:
public partial class NestedControl : UserControl {
public NestedControl() {
CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, CommandBinding_Executed));
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) {
label.Content = "Saved";
}
}
I don't know exactly how CommandTarget works, but binding to the active tab in a TabControl is done with something like this:
"{Binding ElementName=tabControl,Path=SelectedItem}"
(SelectedItem is the current active tab)
EDIT:
More information about CommandTarget can be found here: Setting Command Target in XAML
EDIT 2:
Deleted my initial answer since it was not an answer to the question.

Resources