Bizarre bug when pasting to DataGridTemplateColumn - wpf

I have a DataGridTemplateColumn than contains a UserControl:
<DataGridTemplateColumn Header="Projection"
SortMemberPath="SelectedItem"
ClipboardContentBinding="{Binding ProjectionMethod.Value, Mode=TwoWay}"
>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:DataGridComboBoxCellControl DataContext="{Binding}"
SelectedItem="{Binding ProjectionMethod.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding ProjectionMethodsTextWrapper, Mode=OneWay}"
ErrorMessage="{Binding ProjectionMethod.ErrorMessage, Mode=OneWay}">
</local:DataGridComboBoxCellControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The UserControl bindings are done with DependencyProperties I set up inside the UserControl definition.
For the most part, the control works fine. But when pasting, even though the contents visually appear to be pasted, the cell never commits the paste contents to ProjectionMethod.Value (even though it is set as the ClipboardContentBinding). Debugging reveals that the setter of ProjectionMethod.Value is never even called.
Even more strangely, the constructor of the UserControl is being called during paste. I have no idea why this is occurring. I am pasting to existing cells, no new rows are being created. I was assuming the ClipboardContentBinding routes straight to the underlying property ProjectionMethod.Value. Why the paste command is even bothering with UI controls is a mystery to me.
It seems this problem might require someone with a fairly deep understanding of WPF.
(Here is the current xaml of the UserControl. Right now it's basically a TextBlock and a ComboBox with a few other controls for displaying errors. Any lines with ElementName=parentControl are bindings to dependency properties. Both the TextBlock and ComboBox bind to the same SelectedItem DP.
<UserControl x:Class="DataGridComboBoxCellControl"
x:Name="parentControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyProject"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:MyStringToThicknessConverter x:Key="PopToOne" PopString="1" EmptyString="0"/>
<local:MyStringToVisibilityConverter x:Key="PopToVis" PopString="Visible" EmptyString="Collapsed"/>
</UserControl.Resources>
<Grid ToolTip="{Binding ElementName=parentControl, Path=ErrorMessage, Mode=OneWay}"
IsEnabled="{Binding IsProjectionEnabled}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="3" BorderThickness="{Binding ElementName=parentControl, Path=ErrorMessage, Mode=OneWay, Converter={StaticResource PopToOne}}"
BorderBrush="Red"/>
<Control Grid.Column="0"
Margin="1,0"
Template="{DynamicResource local:MyStyleRef, ResourceKey=errorGrid}"
Visibility="{Binding ElementName=parentControl, Path=ErrorMessage, Mode=OneWay, Converter={StaticResource PopToVis}}"/>
<TextBlock Grid.Column="1" Text="{Binding ElementName=parentControl, Path=SelectedItem, Mode=OneWay}" VerticalAlignment="Center"/>
<ComboBox Grid.Column="2"
Style="{DynamicResource local:MyStyleRef, ResourceKey=noText_ComboBoxStyle}"
ItemsSource="{Binding ElementName=parentControl, Path=ItemsSource, Mode=OneWay}"
SelectedItem="{Binding ElementName=parentControl, Path=SelectedItem}" />
</Grid>
</UserControl>
Update
I have now tried getting rid of the UserControl altogether and dumping its xaml code directly into the DataTemplate of the TemplateColumn - the same issue more or less. Visually the paste appears to execute, but the setter of ProjectionMethod.Value is never called and the viewmodel is therefore never updated. I am using the OnPastingCellClipboardContent command to paste.

I think I figured it out. I set the update trigger for the clipboard paste to PropertyChanged:
ClipboardContentBinding="{Binding ProjectionMethod.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
And now the value is being committed immediately when pasting. I don't know what the default behavior is supposed to be, but before the paste values were never committed, even after row leave. And still not sure why the UserControl's constructor was being called.

Related

Not hitting my Data Template based on datatype in ContentControl, Why not?

None of my datatemplates are showing up based on DataType of the DataContext. The actual object being passed to the DataContext of UserControl is an Entity (EntityFrameWork 6.0).
I am specifying DataType="{x:Type pf:Promotion}" which is the name of a POCO class that the entity is based on.
(xmlns:pf="clr-namespace:PFModel;assembly=PFModel")
I am lost here, don't know where the problem lies. Thankful for any help or hints.
<UserControl x:Class="PFPromoEditor.UserControls.CenterEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PFPromoEditor.UserControls"
xmlns:wpfTool="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
xmlns:pf="clr-namespace:PFModel;assembly=PFModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Border BorderBrush="#FF000000" Margin="2" Padding="5" BorderThickness="1,1,1,1" CornerRadius="8,8,8,8">
<ContentControl>
<ContentControl.Resources>
<DataTemplate DataType="{x:Type pf:Promotion}">
<TextBox Text="Promotion DATA type" />
</DataTemplate>
<DataTemplate DataType="{x:Type pf:Casino}">
<TextBox Text="Casino DATA type" />
</DataTemplate>
<DataTemplate DataType="{x:Type pf:Progressive}">
<TextBlock Text="Progressive DATA type" />
</DataTemplate>
<DataTemplate DataType="{x:Type pf:Detail}">
<TextBox Text="Detail DATA type" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
</Grid>
<local:CenterEditor x:Name="CenterContent" DataContext="{Binding ElementName=promoMenu,Path=MySelectedItem }"/>
Answer to first question, the control has datacontext properly set with an entity, of either Promotion, Casino, Promotion or Detail.
I have also tried it with a bound property like:
<DataTemplate DataType="{x:Type pf:Progressive}">
<Grid>
<TextBlock Text="Progressive DATA type" />
<TextBox Text="{Binding Path=Detail.Title, FallbackValue= 'Select any Item in list to edit'}"/>
</Grid>
</DataTemplate>
Still nothing, blank.
I have also just placed something like:
<TextBox Text="{Binding Path=Detail.Title, FallbackValue= 'Select any Item in list to edit'}"/>
In the code above like thus:
</DataTemplate>
</ContentControl.Resources>
<TextBox Text="{Binding Path=Detail.Title, FallbackValue= 'Select any Item in list to edit'}"/>
</ContentControl>
And the textbox binding is fine, the entity is there and I get the expected data.
Its not a data binding problem. I have context and I have a proper object.
I thought of a couple other things I need to try. Let me get back to you.
It turns out the thing I was doing wrong (besides being stubborn about what I thought was wrong, and trying to solve my problem at 3 am, and over thinking the problem in general) was leaving out:
Content="{Binding}"
which BTW way I missed in the comments, I recall reading datacontext, that happened because I was tired and frustrated.
The thing that solved the problem was this modification to the content control
<ContentControl Content="{Binding}" >

How do I get WPF DataGrid and AutoCompleteBox's Selected Item to play nice?

I've used the AutoCompleteBox without problem on a WPF form. Now I would like to do the same thing inside a WPF DataGrid. Almost everything works except the setter for SelectedItem. I see the getter get called but after typing a value and hitting tab (or using the arrow keys) the setter never gets called. In the console output I see no binding errors. I'm hoping someone can tell me what I'm doing wrong and how to get SelectedItem to fire the setter on the property in ViewModel class when it's inside a DataGrid. First the snippet of the ViewModel class:
public static List<ImpaSimple> AllImpas { get { return ImpaListRepository.ImpaList; } }
private ImpaSimple _selectedImpa;
public ImpaSimple SelectedImpa
{
get { return _selectedImpa; }
set
{
if (value == _selectedImpa) return;
_selectedImpa = value;
//Manually set Description and Unit fields because user can override the IMPA default values.
// Description = _selectedImpa.Name;
//TODO Set Units too
RaisePropertyChanged("SelectedImpa");
}
}
The XAML
In the XAML below I have added a code behind handler for LostFocus as a temporary work-around. The addition of the UpdateSourceTrigger attribute was also an attempt to get this working.
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<TextBlock Style="{StaticResource DataGridHeader}">LImpa</TextBlock>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Hots:AutoCompleteBoxEx ToolTip="Start typing an IMPA number"
ItemsSource="{Binding AllImpas}"
Width="50"
HorizontalContentAlignment="Left"
FilterMode="StartsWith"
IsDropDownOpen="True"
IsTextCompletionEnabled="True"
LostFocus="ImpaBoxExLostFocus"
SelectedItem="{Binding SelectedImpa,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Hots:AutoCompleteBoxEx.ItemTemplate>
<DataTemplate>
<Grid Width="450">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="275" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ImpaId}"
Grid.Column="0" />
<TextBlock Text="{Binding Name}"
Grid.Column="1" />
<TextBlock Text="{Binding Unit}"
Grid.Column="2" />
</Grid>
</DataTemplate>
</Hots:AutoCompleteBoxEx.ItemTemplate>
</Hots:AutoCompleteBoxEx>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Ah, I think I know what that is - the defect I call 'shy datacontext' - try setting your
Hots:AutoCompleteBoxEx DataContext to:
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type YourDataSourceItemType}}}"
The way to check it is create a dummy converter and use it like that:
ItemsSource="{Binding Converter={StaticResource DummyConverter}}"
then put a breakpoint inside its Convert and check for the value. Since no Path is specified - the input value is the DataContext itself, if it's null, then it never gets set/got lost.

Content of ContentControl in Resource

I have a ContentControl in a RadTileView. If I put in some hard coded text into the content property it works fine. (code below)
<ContentControl Grid.Row="2" Grid.Column="0" Content="Hello World"></ContentControl>
That works...if I put the content into the UserControl.Resources section my application freezes up and displays nothing.
<ContentControl Grid.Row="2" Grid.Column="0" Content="{StaticResource TabControlContent}"></ContentControl>
<UserControl.Resources>
<TextBlock x:Key="TabControlContent" Text="hello world"></TextBlock>
</UserControl.Resources>
Ultimately I would like to have the context be a RadTabControl..but for now Id settle on just having that textblock render.
To get a string into your ContentControl you would, add
xmlns:sys="clr-namespace:System;assembly=mscorlib"
to your usings. Then add this
<UserControl.Resources>
<sys:String x:Key="SingleString">Hello World</sys:String>
</UserControl.Resources>
Which would allow
<ContentControl Content="{Binding Source={StaticResource SingleString}}"/>
Hope this helps.

WPF - Databind to a StackPanel using DataTemplates

I've modified my question since it has changed focus when trying things out.
I narrowed the problem down to the following...
I try to bind the selected Item of a TreeView to a StackPanel (or some other container that can hold User Controls). This container will then display a UserControl, depending on the type of the selected item.
Here is the xaml of the StackPanel (both treeview and stackpanel are in the same window ==> different grid column)
<StackPanel Grid.Column="2" MinWidth="500" DataContext="{Binding ElementName=myTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type mvTypes:MyTypeA}">
<controls:UserControlA DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type mvTypes:MyTypeB}">
<controls:UserControlB DataContext="{Binding}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
When I place a user control directly under the stackpanel (not in the resources), it displays it with the selected object as their datacontext.
Idem if I place a TextBox in it, it will show the correct type of the selected item.
<TextBox Name="textBox1" Text="{Binding}" />
For some reason, placing it within a DataTemplate (even without setting the DataType) results in nothing to display.
Any sugestions. I'm thinking that maybe a StackPanel is not the right control for this, though I can't seem to find other controls that look suitable as containers like this.
Thanks in advance.
Replace the StackPanel in your example with ContentPresenter and instead of DataContext set the Content property. That should work.
Although you have set the Binding on the second custom control, are you setting the DataContext, as the binding is the route to the information and the DataContext is the information it applies this binding information to.
Andrew
You can create a UserControl to display the TreeView and the selection info on the right, all in one. It saves you from creating any custom control. A custom control is basically unnecessary since you do not create anything which didn't exist before.
<UserControl x:Class="NameSpace.SelectionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="namespace.Controls"
Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView Name="customTree">
<!--Items go here-->
</TreeView>
<StackPanel Grid.Column="1" MinWidth="50" DataContext="{Binding ElementName=customTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelA}">
<controls:CustomADetailsControl />
</DataTemplate>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelB}">
<controls:CustomBDetailsControl />
</DataTemplate>
</StackPanel.Resources>
<TextBlock Text="{Binding}"/>
</StackPanel>
</Grid>
</UserControl>
Any other custom behaviour, I'm sure you could create or set in styles/templates here.
Also, you might find one of my other answers useful.
Good luck with wpf, cheers.

Find an element in DataTemplate applied to TabItem

I got a problem trying to find an element declared in DataTemplate, that after was applied like a ContentTemplate to TabItem object.
I saw that there is already some solutions in regard of this problem, but no one of them actually works in my case, and I would like to understand why (obviously I make mistake in some place)
Here is a sample code:
<DataTemplate x:Key="TabItemDataTemplate">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Name="templateGrid">
<Grid.RowDefinitions>
<RowDefinition Height="6.0*"> </RowDefinition>
<RowDefinition Height="6" ></RowDefinition>
<RowDefinition Height="6.0*" ></RowDefinition>
<RowDefinition Height="*" ></RowDefinition>
</Grid.RowDefinitions>
<ListView x:Name="repoView" Grid.Row="0"
VerticalAlignment="Stretch"
ItemsSource="{Binding Source={StaticResource DataProviderForListView}}">
<GridView>
<GridViewColumn Header="State"
DisplayMemberBinding="{Binding Path=RepositoryItemState}"/>
<GridViewColumn Header="Working Copy Rev num."
DisplayMemberBinding="{Binding Path=WCRevision}"/>
<GridViewColumn Header="Repository Rev num."
DisplayMemberBinding="{Binding Path=RepoRevision}"/>
<GridViewColumn Header="User"
DisplayMemberBinding="{Binding Path=Account}"/>
<GridViewColumn Header="Item"
DisplayMemberBinding="{Binding Path=ItemName}"/>
</GridView>
</ListView>
<GridSplitter x:Name="gridSplitter" Grid.Row="1"
ResizeDirection="Rows" Background="Gray"
Height="4" HorizontalAlignment="Stretch"
Style="{StaticResource gridSplitterStyle}"/>
<RichTextBox x:Name="rowView" Grid.Row="2"
BorderBrush="Bisque" VerticalAlignment="Stretch"
IsReadOnly="True" Background="YellowGreen"
FontFamily="Comic Sans Serif"/>
<ToggleButton x:Name="rbWorkingCopy"
Template="{StaticResource ToggleButtonControlTemplate}"
Grid.Row="3" Width="100" Height="22"
Content="{StaticResource WorkingCopyTitle}"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
Command="repoManager:AppCommands.GetWorkingCopyInfoCommand" />
<ToggleButton x:Name="rbRepository"
Template="{StaticResource ToggleButtonControlTemplate}"
Grid.Row="3" Width="100" Height="22"
Content="{StaticResource RepositoryTitle}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom" Margin="120,0,0,0"
Command="repoManager:AppCommands.GetRepoInfoCommand" />
<ProgressBar x:Name="checkRepositoryProgress" Grid.Row="3"
Width="220" Height="22" HorizontalAlignment="Right"
VerticalAlignment="Bottom" Margin="250,0,10,0"
IsIndeterminate="True"
IsEnabled="{Binding repoManager:ExecutingCommand}" />
</Grid>
</DataTemplate>
This code is porgrammatically applied to the given TabItem object in following way :
this.ContentTemplate = FindResource("TabItemDataTemplate") as DataTemplate;
After I need access to the ListView element declared in DataTemplate, so I execute the codes found around in internet, and also on this site. Here is a short example:
/* Getting the ContentPresenter of myListBoxItem*/
ContentPresenter myContentPresenter =
FindVisualChild<ContentPresenter>(this);
// this.GetVisualChild(0)
/* Finding textBlock from the DataTemplate that is set on that ContentPresenter*/
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
ListView repoListView = (ListView)myDataTemplate.FindName("repoView",
myContentPresenter);
Problem1: In this case ContentTemplate of ContentPresenter is Null, so code execution crashes.
Prolem2: Ok, I think, may be I need to navigate throw TabItem content directly, so the code becomes, more or less:
/* Getting the ContentPresenter of myListBoxItem*/
ContentPresenter myContentPresenter =
FindVisualChild<ContentPresenter>(this);
// this.GetVisualChild(0)
/* Finding textBlock from the DataTemplate that is set on that ContentPresenter*/
DataTemplate myDataTemplate = this.ContentTemplate;
ListView repoListView = (ListView)myDataTemplate.FindName("repoView",
myContentPresenter);
this is TabItem object. But the strage things, that the ContentTemplate of this is completely different from that one assigned above. I'm sure that I missed something somewhere, can you help me to figure out the problem ?
Thank you.
You don't want to use any of the template properties of the TabItem, since those are used to create the actual controls, rather than storing them. You should be able to search the visual tree for the ListView directly, rather than going through the DataTemplate.
Ok, here we come :)
I resolve the problem, in not very nice way, but it seems that works correctly.
As I mentioned above I used LoadContent method and it returns me the ListView object, but by the way it wasn't the ListView that UI actually uses. So to resolve that problem I add static property to hold my REAL ListView object (static as I have single DataTemplate that contains ListView shared across multiple TabItems, so the ListView shared too) and add event handler to my DataTemplate -> Loaded. Catching this event, that in my case raises only ones in lifetime of application, in RoutedEvent's OriginalSource I got the REAL ListView object that WPF engine uses for rendering on UI.
Hope my solution will help someone.
Thank you all.
Simply, if you have a DataGrid, and a TemplateColumn which contains a data template, you can use the following code sample:
<DataGridTemplateColumn x:Name="photoPathColumn" Header="{x:Static resx:FrmResource.Photo}" Width="Auto">
<DataGridTemplateColumn.CellEditingTemplate x:Uid="keyelm">
<DataTemplate x:Name="dodo">
<StackPanel Orientation="Horizontal" Height="Auto">
<TextBlock x:Name="photo" x:Uid="imageFile" Text="{Binding Path=PhotoPath}"></TextBlock>
<Button x:Name="Browse" Content="..." Click="Browse_Click"></Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
TextBlock tBlock = (TextBlok)photoPathColumn.CellEditingTemplate.FindName(
"photo",
photoPathColumn.GetCellContent(CustomersDataGrid.CurrentItem));
Where photo is the name of text block
Where photoPathColumn is the DataGrid's TemplateColumn.

Resources