DataTrigger not reevaluating after property changes - wpf

[Original]
I have a ListBox which has its ItemsSource (this is done in the code behind on as the window is created) databound to an ObservableCollection. The ListBox then has the following DataTemplate assigned against the items:
usercontrol.xaml
<ListBox x:Name="communicatorListPhoneControls"
ItemContainerStyle="{StaticResource templateForCalls}"/>
app.xaml
<Style x:Key="templateForCalls" TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate" Value="{StaticResource templateRinging}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=hasBeenAnswered}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource templateAnswered}"/>
</DataTrigger>
</Style.Triggers>
</Setter>
</Style>
When the ObservableCollection is updated with an object, this appears in the ListBox with the correct initial DataTemplate, however when the hasBeenAnswered property is set to true (when debugging i can see the collection is correct) the DataTrigger does not re-evaluate and then update the ListBox to use the correct DataTemplate.
I have implemented the INotifyPropertyChanged Event in my object, and if in the template is bound to a value, i can see the value update. Its just that the DataTrigger will not re-evaluate and change to the correct template.
I know the DataTrigger binding is correct because if i close the window and open it again, it will correctly apply the second datatemplate, because the hasBeenAnswered is set to true.
[edit 1]
Following on from comments made by Timores I've tried the following:
usercontrol.xaml
<ListBox x:Name="communicatorListPhoneControls"
ItemTemplate="{StaticResource communicatorCallTemplate}"/>`
app.xaml:
<DataTemplate x:Key="communicatorCallTemplate">
<Label x:Name="test">Not answered</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=hasBeenAnswered}" Value="True">
<Setter TargetName="test" Property="Background" Value="Blue"/>
</DataTrigger>
</DataTemplate.Triggers>
</Label>
</DataTemplate>
What happens now is similar to the first example, when a call comes in the "Not answered" label shows (one per call that exists as this is a listbox - normally when the window loads there will be no calls), the call is then answered and the proptery hasBeenAnswered is set to true, yet the "Not Answered" remains the same. If i close the window, and re-open it again (with the active call still with the property hasBeenAnswered set to true) the background is then blue. So it would appear to me like the datatrigger is simply not being run, until the window is re-run.

What seems strange to me in the example is that you are using an ItemContainerStyle instead of an ItemTemplate.
ItemContainerStyle applies to the ListBoxItem that contains each element in your ItemsSource. The ListboxItem does not have an hasBeenAnswered property, so I don't see how the binding could work.
I suggest creating a DataTemplate for the data type in your list box and using triggers to make the same changes as in your templateAnswered style.
Edit: after OP used the suggestion of the ItemTemplate.
I tried to reproduce the example, and it works fine for me.
Here is my XAML (please disregard style, this is just an example):
Not answered
<ListBox x:Name="communicatorListPhoneControls"
ItemTemplate="{StaticResource communicatorCallTemplate}"/>
<Button Margin="0,20,0,0" Click="OnToggleAnswer" Content="Toggle answer status" />
</StackPanel>
And in the code-behind:
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
List<PhoneCall> lpc = new List<PhoneCall>()
{new PhoneCall(), new PhoneCall(), new PhoneCall(), new PhoneCall()};
communicatorListPhoneControls.ItemsSource = lpc;
}
private void OnToggleAnswer(object sender, RoutedEventArgs e) {
object o = communicatorListPhoneControls.SelectedItem;
if (o != null) {
PhoneCall pc = (PhoneCall) o;
pc.hasBeenAnswered = ! pc.hasBeenAnswered;
}
}
}
public class PhoneCall : INotifyPropertyChanged {
private bool _answered;
public bool hasBeenAnswered {
get { return _answered; }
set {
if (_answered != value) {
_answered = value;
FirePropertyChanged("hasBeenAnswered");
}
}
}
private void FirePropertyChanged(string propName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Could you try to reproduce this and compare with your code ?
Note: the smallest error in the property name given to PropertyChanged could explain your behaviour. The trigger could be based on the right property, but the notification could have a misspelled name.

Related

Why do I need to specify ElementName and DataContext in a binding?

To familiarize myself with WPF and MVVM concepts I built a visual representation of a Sudoku board.
My (simplified) setup looks like this (no custom code-behind in views anywhere):
I have a MainWindow.xaml:
<Window x:Class="Sudoku.WPF.MainWindow">
<Window.DataContext>
<models:MainWindowViewModel/>
</Window.DataContext>
<ctrl:SudokuBoard DataContext="{Binding Path=GameViewModel}"/>
</Window>
My MainWindowViewModel:
class MainWindowViewModel
{
public MainWindowViewModel()
{
IGame g = new Game(4);
this.GameViewModel = new GameViewModel(g);
}
public IGameViewModel GameViewModel { get; private set; }
}
SudokuBoard is a UserControl. Its DataContext is set to GameViewModel as per above.
Relevant parts of GameViewModel, Elements is populated in the ctor, Possibilities is set via a command:
public IList<CellViewModel> Elements { get; private set; }
private bool _showPossibilities;
public bool ShowPossibilities
{
get { return _showPossibilities; }
set
{
_showPossibilities = value;
OnPropertyChanged();
}
}
In SudokuBoard.xaml I have:
<ItemsControl x:Name="SudokuGrid" ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource ToggleContentStyle}"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Elements is a collection of CellViewModels generated in the constructor of GameViewModel.
Now to the question: my ToggleContentStyle as defined in <UserControl.Resources>:
<Style x:Key="ToggleContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource valueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource possibilityTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
(both ContentTemplates just show other properties of a single CellViewModel in different representations)
Question 1: I have to explicitly reference DataContext in order to get to the ShowPossibilities property. If I leave it out, so that Path=ShowPossibilities, I get a UniformGrid with the ToString() representation of CellViewModel. My assumption is that that is because the style is referenced from the ItemTemplate, with it's binding set to a single CellViewModel. Is that assumption valid?
Question 2: When I omit the ElementName part, I also get the ToString() representation of CellViewModel. Now I'm really confused. Why is it needed?
Datacontext is a dependency property which is marked as inherits. That means its inherited down the visual tree.
When you bind the default place it's going to look for a source is in the datacontext.
This is the simple situation.
Say you have a window and that has datacontext set to WindowViewmodel and stick a textbox in that Window. You bind it's Text to FooText. This means the textbox goes and looks for a FooText property in that instance of WindowViewmodel.
All pretty simple so far.
Next...
You use elementname.
What that does is says go and take a look at this element. Look for a property on that. If you did that with our textbox above then it would expect a dependency property FooText on whatever you point it to.
Datacontext is a dependency property.
And when you do:
"{Binding FooProperty
This is shorthand for:
"{Binding Path=FooProperty
Where FooProperty is a property path, not =just the name of a property.
Which is maybe worth googling but means you can use "dot notation" to walk down the object graph and grab a property on an object ( on an object.... ).
Hence DataContext.Foo or Tag.Whatever ( since tag is another dependency property a control will have ).
Let's move on to some other complications.
The datcontext is inherited down the visual tree but there's a few of gotchas here. Since
some things look like they're controls but are not ( like datagridtextcolumn ). Templated things can be tricky. Itemscontrols are a kind of obvious and relevent special case.
For an itemscontrol, the datacontext of anything in each row is whichever item it's presented to from the itemssource. Usually you're binding an observablecollection of rowviewmodel to that itemssource. Hence ( kind of obviously ) a listbox or datagrid shows you the data from each rowviewmodel you gave it in each row.
If you then want to go get a property is not in that rowviewmodel you need to tell it to look somewhere else.
When you specify an element in Binding (eg ElementName=SudokuGrid), the Path has to refer to any property of that element. Because this element is a wpf control, DataContext is one of it's properties but ShowPossibilities isn't. So if you do just Path=ShowPossibilities it will not be able to find that path at all.
If you don't specify element in Binding at all then it defaults to the DataContext associated with the control. If the associated DataContext doesn't have the property ShowPossibilities it will not be able to find it.
PS: If you want to debug wpf UI to see what the DataContext is at run-time you could use utility like Snoop.

Changing another control's property in some control's Trigger with "dynamic" name

I am trying to change my labels style dynamically on my forms.
The behaviour I want is: Every time a textbox called 'txtName', for instance, gets Focused, it should search for a Label Control named 'lblName' and change its FontWeight property to "Bold".
The same for a textbox called 'txtBirthday' and a label called 'lblBirthday', where 'txt' stands for TextBox and lbl for Label.
Every textbox has a NAME and a prefix "txt" and a prefix "lbl" for its corresponding label, but if the textbox doesnt find a correspoding label it should do nothing.
In other words, every time a Textbox get focused on the form, it should search for the label "responsable" for its description and hightlight it (changing its font weight to bold) so the form will be more user frendly. That way the user wont get confused which textbox he is typing in.
I have a peace of code that maybe a good start point, but I dont know how to work with non-static control names.
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<!-- Here is how we bind to another control's property -->
<DataTrigger Binding="{Binding IsFocused, ElementName=txtUser}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<!-- Here is the 'override' content -->
</DataTrigger>
</Style.Triggers>
</Style>
As mentioned in the comments above, the technique of searching for and pattern matching element names as the basis for applying visual behaviour is not robust. For example, what happens when you make a typo and use "lbel" instead of "lbl"? Or what happens if you later decide to replace all Labels with TextBlocks - do you still annotate their names with a prefix of "lbl" to preserve the behaviour? Another downside to using code to change visuals - is now understanding the behaviour of your UI from reading XAML alone becomes much harder since properties are being changed behind the scenes. WPF has many built in ways which should be preferred over this approach. If you are interested in alternative implementations, just ask we are here to help :)
That being said, if must use this approach, here is what your attached behaviour would look like:
C#
public static class FontWeightFocusedHelper
{
private static readonly List<Label> Labels = new List<Label>();
public static void SetChangeFontWeightOnTextBoxFocused(Label label, bool value)
{
label.SetValue(ChangeFontWeightOnTextBoxFocusedProperty, value);
}
public static bool GetChangeFontWeightOnTextBoxFocused(Label label)
{
return (bool) label.GetValue(ChangeFontWeightOnTextBoxFocusedProperty);
}
public static readonly DependencyProperty ChangeFontWeightOnTextBoxFocusedProperty =
DependencyProperty.RegisterAttached("ChangeFontWeightOnTextBoxFocused", typeof (bool),
typeof (FontWeightFocusedHelper),
new FrameworkPropertyMetadata(OnChangeFontWeightOnTextBoxFocusedPropertyChanged));
private static void OnChangeFontWeightOnTextBoxFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox)
{
var textBox = (TextBox) d;
// Make sure to use a WeakEventManager here otherwise you will leak ...
textBox.GotFocus += OnTextBoxGotFocusChanged;
textBox.LostFocus += OnTextBoxLostFocusChanged;
return;
}
if (d is Label)
{
// Make sure to store WeakReferences here otherwise you will leak ...
Labels.Add((Label)d);
return;
}
throw new InvalidOperationException("ChangeFontWeightOnTextBoxFocused can only be set on TextBox and Label types.");
}
private static void OnTextBoxLostFocusChanged(object sender, RoutedEventArgs e)
{
SetMatchingLabelFontWeight(sender as TextBox, FontWeights.Regular);
}
private static void OnTextBoxGotFocusChanged(object sender, RoutedEventArgs e)
{
SetMatchingLabelFontWeight(sender as TextBox, FontWeights.Bold);
}
private static void SetMatchingLabelFontWeight(TextBox textBox, FontWeight fontWeight)
{
if (textBox != null)
{
// Suggest adding a property for LabelPrefix and TextBoxPrefix too, use them here
var label = Labels.Where(l => !String.IsNullOrEmpty(l.Name))
.Where(l => l.Name.Replace("lbl", "txt") == textBox.Name)
.FirstOrDefault();
if (label != null)
{
label.FontWeight = fontWeight;
}
}
}
}
XAML
<StackPanel >
<StackPanel.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="l:FontWeightFocusedHelper.ChangeFontWeightOnTextBoxFocused" Value="True" />
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="l:FontWeightFocusedHelper.ChangeFontWeightOnTextBoxFocused" Value="True" />
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<Label x:Name="lblOne" VerticalAlignment="Center" Content="First Name"/>
<TextBox x:Name="txtOne" Width="300" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label x:Name="lblTwo" VerticalAlignment="Center" Content="Last Name" />
<TextBox x:Name="txtTwo" Width="300" VerticalAlignment="Center" />
</StackPanel>
</StackPanel>
Hope this helps!
You can have all gotfocus go to the same event. The sender is passed to the event so you can get the name of the sender. In code behind you can use variable and logic that is not available in XAML.

In WPF, how do I find an element in a template that's switched in via a trigger?

I have a UserControl (not a lookless custom control) which, depending on some custom state properties, swaps in various ContentTemplates, all defined as resources in the associated XAML file. In the code-behind, I need to find one of the elements in the swapped-in ContentTemplates.
Now in a lookless control (i.e. a custom control), you simply override OnApplyTemplate then use FindName, but that override doesn't fire when the ContentTemplate gets switched by a trigger (...at least not for a UserControl. I haven't tested that functionality with a custom control.)
Now I've tried wiring up the Loaded event to the control in the swapped-in template, which does fire in the code-behind, then I simply store 'sender' in a class-level variable. However, when I try to clear that value by subscribing to the Unloaded event, that doesn't fire either because the tempalte gets swapped out, thus unwiring that event before it has a chance to be called and the control unloads from the screen silently, but I still have that hung reference in the code-behind.
To simulate the OnApplyTemplate functionality, I'm considering subscribing to the ContentTemplateChanged notification and just using VisualTreeHelper to look for the control I want, but I'm wondering if there's a better way, hence this post.
Any ideas?
For reference, here's a very-stripped-down example of the control I have. In this example, if IsEditing is true, I want to find the textbox named 'FindMe'. If IsEditing is false which means the ContentTemplate isn't swapped in, I want to get 'null'...
<UserControl x:Class="Crestron.Tools.ProgramDesigner.Controls.EditableTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Crestron.Tools.ProgramDesigner.Controls"
x:Name="Root">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox x:Name="FindMe"
Text="{Binding Text, ElementName=Root}" />
</DataTemplate>
<Style TargetType="{x:Type local:EditableTextBlock}">
<Style.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<TextBlock x:Name="TextBlock"
Text="{Binding Text, ElementName=Root}" />
</UserControl>
Aaaaaaand GO!
M
Unfortunately, there isn't a better way. You can override the OnContentTemplateChanged, instead of hooking up to the event.
You would need to use the DataTemplate.FindName method to get the actual element. The link has an example of how that method is used.
You would need to delay the call to FindName if using OnContentTemplateChanged though, as it is not applied to the underlying ContentPresenter immediately. Something like:
protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
this.Dispatcher.BeginInvoke((Action)(() => {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in OnContentTemplateChanged";
}), DispatcherPriority.DataBind);
}
Alternatively, you may be able to attach a handler to the LayoutUpdated event of the UserControl, but this may fire more often than you want. This would also handle the cases of implicit DataTemplates though.
Something like this:
public UserControl1() {
InitializeComponent();
this.LayoutUpdated += new EventHandler(UserControl1_LayoutUpdated);
}
void UserControl1_LayoutUpdated(object sender, EventArgs e) {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in UserControl1_LayoutUpdated";
}

WPF: Once I set a property in code, it ignores XAML binding forever more... how do I prevent that?

I have a button that has a datatrigger that is used to disable the button if a certain property is not set to true:
<Button Name="ExtendButton" Click="ExtendButton_Click" Margin="0,0,0,8">
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
That's some very simple binding, and it works perfectly. I can set "IsConnected" true and false and true and false and true and false, and I love to see my button just auto-magically become disabled, then enabled, etc. etc.
However, in my Button_Click event... I want to:
Disable the button (by using ExtendButton.IsEnabled = false;)
Run some asynchronous code (that hits a server... takes about 1 second).
Re-enable the button (by using ExtendButton.IsEnabled = true;)
The problem is, the very instant that I manually set IsEnabled to either true or false... my XAML binding will never fire again. This makes me very sad :(
I wish that IsEnabled was tri-state... and that true meant true, false meant false and null meant inherit. But that is not the case, so what do I do?
Benny's answer provides one approach (and an excellent one). Another is to extend your model to support the "in async operation" state and to add another trigger to respond to that:
XAML:
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding IsInAsyncOperation}" Value="True">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
Code-behind:
private void ExtendButton_Click(...)
{
IsInAsyncOperation = true;
// begin async operation
}
private void OnAsyncOperationComplete(...)
{
// retrieve results etc.
IsInAsyncOperation = false;
}
Note: You'd define the IsInAsyncOperation property on the same class as IsConnected; if that's a view model rather than the Window class, then you'll need to tweak the code-behind accordingly.
There's a much better way to get this functionality in WPF. It's the commanding system, and it's awesome. Any button, menu item, hot key, etc can be linked to a single command which automatically handles enabling/disabling (as you desire in your program). It's also clean and reusable, and sooooo easy to use.
For example, in an application of mine, I have an "about dialog" that shows up when the user hits F1. I created a command called AboutCommand by implementing ICommand.
public class AboutCommand:System.Windows.Input.ICommand
{
public bool CanExecute(object parameter)
{
return true; // in this case the command is never disabled
}
public event EventHandler CanExecuteChanged
{
// not needed in this case, but in commands when CanExecute actually
// changes, this performs the "magic" of disabling/enabling your controls
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
new AboutWindow().ShowDialog(); //happens when the command is executed
}
}
Then, in my window's xaml, I added*:
<Window.Resources>
<local:AboutCommand x:Key="About"/>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Command="{StaticResource About}" Gesture="F1"/>
</Window.InputBindings>
You could also set the command to a button like so.
<Button Command="{StaticResource About}" Content="About this program"/>
Both the F1 key and the button would be disabled if AboutCommand.CanExecute() returned false.
*(I actually did it differently, because I'm using the MVVM pattern, but this works if you aren't using that pattern.)

Where are the margins/padding set on a wpf ListView GridView?

I've got a WPF ListView/GridView spec'd in XAML. The first column uses a CellTemplate to specify icons and the others use DisplayMemberBinding to populate themselves. The icons column is 20 wide, the icons 16 but they're getting truncated by margins/padding/something. I can't work out where it's set.
Here's the essentials (I've removed some columns because they're the same):
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.Resources>
<DataTemplate x:Key="image">
<Image Width="16" Height="16" Margin="0,0,0,0"
HorizontalAlignment="Center"
Source="{Binding Path=ObjectType,
Converter={StaticResource imageConverter} }" />
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Width="20"
CellTemplate="{StaticResource image}"/>
<GridViewColumn Width="120" Header="Name"
DisplayMemberBinding="{Binding Path=Name}"/>
</GridView>
</ListView.View>
ImageConverter just turns an ObjectType into an image so each type of item gets its own icon.
Many features of the GridView are hard-coded into the class. In particular the GridViewRowPresenter creates either a hard-coded TextBlock or ContentPresenter container for each of the cells and forces the margins to 6,0,6,0 whether you like it or not.
Normally I would discourage you from anything that involves hard-coding a negative margin to compensate as it's a hack layered on another hack. However, after disassembling the GridViewRowPresenter, there are no other options.
My first attempt was to override the implicit style for the container control that was created in the GridViewRowPresenter class, but that control is a TextBox or a ContentPresenter which has no template to override.
My second attempt was to disassemble the code and build a new GridViewRowPresenter. I'm afraid there are just too many incestuous 'internal' calls in the class to make this a workable solution.
A smarter architect would have provided a DependencyProperty that allowed the user of the class to override the container that was created for each cell. For some reason they allowed this with the headers but not the actual cell contents.
So we are left with the fact that the cells in a GridRowPresenter can neither be overridden, reverse engineered, nor templated. I'm sorry, but the negative margins is the best that can be done with this class.
We need a better ListView: the GridView is not suitable for a lookless interface though I appreciate what they original architects were trying to do by divorcing the ItemsPresenter from the presentation.
by default, each cell has a hardcoded margin of 6,0. Probably that's your problem. You can override this behaviour by setting a margin of -6,0 in your celltemplate
Use Snoop to figure out which element is responsible for imposing the extra spacing. Mouse over the space and hold down control and shift. The element will then be highlighted in the visual tree.
I developed a solution based on Ian Griffiths's hack ( http://www.interact-sw.co.uk/iangblog/2007/05/30/wpf-listview-column-margins ).
The control does the following:
sets hard-coded Margin for TextBlock or ContentPresenter created by GridViewRowPresenter to this control's Padding dependency property
propagates this control's HorizontalAlignment and VerticalAlignment to its containers
resets Margin/Padding for ListViewItem/GridViewRowPresenter containers.
So it gives you a kind of control of values which are hard-coded.
Code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Put.Your.Namespace.Here
{
/// <summary>
/// Class allows for reseting hard coded ListViewItem margins and paddings
/// </summary>
public class ListViewCustomizableCellPresenter : Decorator
{
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
var cp = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (cp != null)
{
cp.Margin = Padding;
cp.VerticalAlignment = VerticalAlignment;
cp.HorizontalAlignment = HorizontalAlignment;
}
ResetGridViewRowPresenterMargin();
ResetListViewItemPadding();
}
private T FindInVisualTreeUp<T>() where T : class
{
DependencyObject result = this;
do
{
result = VisualTreeHelper.GetParent(result);
}
while (result != null && !(result is T));
return result as T;
}
private void ResetGridViewRowPresenterMargin()
{
var gvrp = FindInVisualTreeUp<GridViewRowPresenter>();
if (gvrp != null)
gvrp.Margin = new Thickness(0);
}
private void ResetListViewItemPadding()
{
var lvi = FindInVisualTreeUp<ListViewItem>();
if (lvi != null)
lvi.Padding = new Thickness(0);
}
/// <summary>
/// Padding dependency property registration
/// </summary>
public static readonly DependencyProperty PaddingProperty =
DependencyProperty.Register("Padding", typeof(Thickness), typeof(ListViewCustomizableCellPresenter), new PropertyMetadata(default(Thickness)));
/// <summary>
/// Padding dependency property
/// </summary>
public Thickness Padding
{
get { return (Thickness)GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
}
}
Sample usage in xaml (inside definition of GridViewColumn):
<GridViewColumn.CellTemplate>
<DataTemplate>
<yourxamlnamespacehere:ListViewCustomizableCellPresenter Padding="0"
VerticalAlignment="Center">
<YourControlOrPanelHere />
</yourxamlnamespacehere:ListViewCustomizableCellPresenter>
</DataTemplate>
</GridViewColumn.CellTemplate>
Since the margin is a hard coded constant, only solution is to use reflection to change it by hand.
In VB :
' Hack :
' Changes the default margin for the GridView's Column.
Dim GridViewCellMarginProperty = GetType(GridViewRowPresenter).GetField("_defalutCellMargin", BindingFlags.NonPublic Or BindingFlags.Static Or BindingFlags.GetField)
If (GridViewCellMarginProperty IsNot Nothing) Then
GridViewCellMarginProperty.SetValue(Nothing, New Thickness(2, 0, 2, 0))
End If
It works fine on my project.
The solution I came to is to inherit GridViewRowPresenter and override OnVisualChildrenChanged to set the margin to 0 when a children is added :
public class MyGridViewRowPresenter : GridViewRowPresenter
{
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
if (visualAdded is FrameworkElement frameworkElement)
frameworkElement.Margin = new Thickness(0);
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
}
}

Resources