Change DataTemplate only if element contains background (hex) - c#

<DataTemplate x:Key="dataTempl">
<!--<Border BorderBrush="Coral" BorderThickness="1" Width="Auto" Margin="2">-->
<Button Background="{Binding background}" Name="btn" Tag="{Binding oID}" Click="btn_Click" Style="{StaticResource MetroButton}" Margin="1">
(... rest of items here ...)
</StackPanel>
</Button>
<!--</Border>-->
</DataTemplate>
As you can see, button have Style and background. Style from Resources contain border, background (as gradient) etc.
Now background element from my class:
public Brush background
{
get
{
SolidColorBrush clr = null;
if (backgroundString != "")
{
clr = new SolidColorBrush((Color)ColorConverter.ConvertFromString(backgroundString));
}
return clr;
}
}
But problem is that, it could contains color like #FFFF0000 or just be null.
What I'd like to do is :
if (backgroundString != "") -> apply background
else leave style as it was before.
But with code I show you, if it return null, style does change (there is no borders etc.)
Any idea?
Thanks!

What you want to do is a trigger.
You would like to use the default background, but override it when a given property meet a given condition.
You can do this easily with a trigger.
Simply add a property such as this one to your view model:
public bool OverrideBackground { get { return backgroundString != ""; } }
Then add the following trigger in your DataTemplate:
<DataTemplate>
[...]
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding OverrideBackground}" Value="true">
<Setter Property="Button.Background" Value="{Binding background}" TargetName="btn"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
The DataTrigger will be activated when the OverrideBackground property is true (that is, when backgroundString != ""), and will set the Background property of the Button (that you named btn in your code snippet) to the value of the background property of the bound view model.

Related

Hide empty group in WPF ListBox

I'm trying to further customize build-in capability of WPF ListBox for showing items in groups.
In short, I want to hide Group's container (and Group's title altogether) if all items inside group are collapsed (Visibility property).
First, I have very simple class City that represent single Item. This class include Shown property. Inside ItemContainerStyle I simply have DataTrigger that set Visibility to Collapsed if value of this property is False.
class City : INotifyPropertyChanged
{
private bool m_Shown = true;
public string Name { get; set; }
public string Country { get; set; }
public bool Shown
{
get
{
return m_Shown;
}
set
{
m_Shown = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Shown"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This is how I add sample cities, add Group description and all work fine.
m_cities = new List<City>
{
new City() { Name = "Berlin", Country = "Germany" },
new City() { Name = "Milano", Country = "Italy" },
new City() { Name = "Frankfurt", Country = "Germany" },
new City() { Name = "Rome", Country = "Italy" }
};
ICollectionView view = CollectionViewSource.GetDefaultView(m_cities);
view.GroupDescriptions.Add(new PropertyGroupDescription("Country"));
Cities = view; // <-- Binds to ItemsSource of ListBox
I tried in several ways to automatically hide Group if there are no more items visible in it (all are collapsed), but all without luck.
One way is to repeat last 3 lines in code above and this works, but I noticed slowdown with this method and listbox must work fast for user.
Bellow is one of my examples and this actually worked for hiding, but I can't bring group to be visible anymore after that. I tried with converters and similar, but I can't get group visible again.
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Style.Triggers>
<Trigger Property="ActualHeight" Value="20">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<TextBlock Text="{Binding Path=Name}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
Thanks for any help.
A bit (!) late with this, but hopefully it might help someone else in the future.
Inside the control template of most (all?) GroupItem styles is an ItemsPresenter that is used to host and display the child items that belong to the group. It stands to reason that, if all of the child items are collapsed, this ItemsPresenter will have a height of zero.
Therefore, you can add a trigger to the control template based on this condition, and set the Visibility of the whole group item accordingly. A normal property trigger doesn't seem to work, but a data trigger will. Something like this:
<ControlTemplate>
<StackPanel x:Name="Root">
...
<ItemsPresenter x:Name="ItemsPresenter" />
...
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ActualHeight, ElementName=ItemsPresenter}" Value="0">
<Setter TargetName="Root" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
You will need to name the root element of the control template (in this example it's a StackPanel element I've named "Root") and also the ItemsPresenter element (I've just called it "ItemsPresenter"). Obviously the root element might be a different type, and you can use whichever names you like.
You were on the right track, but you needed to bind to the ActualHeight of the ItemsPresenter, and it needed to be a data trigger not a normal property trigger.

UWP equivalent function to FindAncestor in uwp

I have a list of orders and when the order status is Cancelled, I want to blink the text. So far, my code works. However, sometimes it will throws exception:
WinRT information: Cannot resolve TargetName lblOrderStatus
For some reason lblOrderStatus can be found. So, I want to use "FindAncestor", but FindAncestor doesn't exists in UWP. Is there any equivalent function to FindAncestor in uwp?
Here is my code:
<ItemsControl x:Name="Orders" Grid.Row="1" Background="Transparent">
...
...
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
...
...
...
<Viewbox Grid.Column="3" StretchDirection="DownOnly" HorizontalAlignment="Right">
<TextBlock x:Name="lblOrderStatus" Text="{Binding Path=OrderItemStatus, Mode=OneWay}" FontSize="18">
<TextBlock.Resources>
<Storyboard x:Name="sbBlinking">
<DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Opacity)"
Storyboard.TargetName="lblOrderStatus"
From="1" To="0" AutoReverse="True" Duration="0:0:0.5" RepeatBehavior="Forever" />
</Storyboard>
</TextBlock.Resources>
<interactive:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding OrderItemStatus, Converter={StaticResource EnumToStringConverter}}" ComparisonCondition="Equal" Value="Cancelled">
<media:ControlStoryboardAction Storyboard="{StaticResource sbBlinking}" />
</core:DataTriggerBehavior>
</interactive:Interaction.Behaviors>
</TextBlock>
</Viewbox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Considering all the solutions I've seen, I feel that using ElementName binding is the simplest workaround to UWP not having a RelativeSource AncestorType binding option.
Assuming you've got a Page with its DataContext set to a viewmodel with a command MyCommand, and you want each item in your list to execute it when its button is clicked:
<Page Name="thisPage">
...
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ElementName=thisPage, Path=DataContext.MyCommand}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Page>
My initial problem with this solution is that you can't extract the DataTemplate out as a resource to use it on multiple screens (or even dialog boxes); thisPage might not exist in each of those places, or it might not be appropriate to name the root element "thisPage".
But if you use a convention where you include a token UI element in every screen that uses that DataTemplate, and refer to it by a consistent name, it will work. By default this element's DataContext will be your ViewModel (assuming your root element does too)
<Rectangle Name="VmDcHelper" Visibility="Collapsed"/>
...then in your standalone resources XAML file you can write your DataTemplate like this:
<DataTemplate x:Key="MyDataTemplate">
<Button Command="{Binding ElementName=VmDcHelper, Path=DataContext.MyCommand}" />
</DataTemplate>
Then, on every page/screen/dialog that you use that template resource, just drop in a copy of that Rectangle (or whatever) and everything will bind correctly at run-time
This is clearly a hack solution, but after thinking about it some more, it doesn't feel like any more of a hack than using WPF's AncestorType in the first place (having to ensure that your ancestor type is always consistent in all the places you use your DataTemplate).
I'm converting an app from WPF to UWP and found this thread. It seems there are no good solutions on the web, so here is my attempt to 'solve' this problem via workaround.
NOTE: The following is UNTESTED in UWP (but works in WPF) as I'm part way through a large non-compiling port, but theoretically it should work...
1 Create a RelativeSourceBinding Attached Property
This class has two properties: AncestorType and Ancestor. When the AncestorType changes, we subscribe to FrameworkElement.Loaded (to handle parent changes) and find the visual parent of type and assign to the Ancestor attached property.
public class RelativeSourceBinding
{
public static readonly DependencyProperty AncestorTypeProperty = DependencyProperty.RegisterAttached("AncestorType", typeof(Type), typeof(RelativeSourceBinding), new PropertyMetadata(default(Type), OnAncestorTypeChanged));
public static void SetAncestorType(DependencyObject element, Type value)
{
element.SetValue(AncestorTypeProperty, value);
}
public static Type GetAncestorType(DependencyObject element)
{
return (Type)element.GetValue(AncestorTypeProperty);
}
private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)d).Loaded -= OnFrameworkElementLoaded;
if (e.NewValue != null)
{
((FrameworkElement)d).Loaded += OnFrameworkElementLoaded;
OnFrameworkElementLoaded((FrameworkElement) d, null);
}
}
private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e)
{
var ancestorType = GetAncestorType((FrameworkElement) sender);
if (ancestorType != null)
{
var findAncestor = ((FrameworkElement) sender).FindVisualParent(ancestorType);
RelativeSourceBinding.SetAncestor(((FrameworkElement)sender), findAncestor);
}
else
{
RelativeSourceBinding.SetAncestor(((FrameworkElement)sender), null);
}
}
public static readonly DependencyProperty AncestorProperty = DependencyProperty.RegisterAttached("Ancestor", typeof(UIElement), typeof(RelativeSourceBinding), new PropertyMetadata(default(FrameworkElement)));
public static void SetAncestor(DependencyObject element, UIElement value)
{
element.SetValue(AncestorProperty, value);
}
public static UIElement GetAncestor(DependencyObject element)
{
return (UIElement)element.GetValue(AncestorProperty);
}
}
Where FindVisualParent is an extension method defined as
public static UIElement FindVisualParent(this UIElement element, Type type)
{
UIElement parent = element;
while (parent != null)
{
if (type.IsAssignableFrom(parent.GetType()))
{
return parent;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
2 Apply the RelativeSourceBinding property in XAML
some BEFORE xaml in WPF would look like this
<Style x:Key="SomeStyle" TargetType="local:AClass">
<Style.Setters>
<Setter Property="SomeProperty" Value="{Binding Foo, RelativeSource={RelativeSource AncestorType=local:AnotherClass}}" />
</Style.Setters>
</Style>
and AFTER xaml
<Style x:Key="SomeStyle" TargetType="local:AClass">
<Style.Setters>
<Setter Property="apc:RelativeSourceBinding.AncestorType" Value="local:AnotherClass"/>
<Setter Property="Foreground" Value="{Binding Path=(apc:RelativeSourceBinding.Ancestor).Foo, RelativeSource={RelativeSource Self}}" />
</Style.Setters>
</Style>
It's a bit messy but in the case where you only have one RelativeSource FindAncestor type to find, it should work.
In XAML
You can try using RelativeSource, it provides a means to specify the source of a binding in terms of a relative relationship in the run-time object graph.
For example using TemplatedParent:
Height="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Parent.ActualHeight}
or
<Binding RelativeSource="{RelativeSource TemplatedParent}" ></Binding>
In code you try using the VisualTreeHelper.GetParent method.
https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.visualtreehelper.getparent
something like the following, here is an example of a utility function
internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
{
T asType = (T)current;
results.Add(asType);
}
FindChildren<T>(results, current);
}
}
The following example shows code that checks for an element's parent
((StackPanel)LinePane.Parent).ActualWidth;
Also, here is a good blog post showing this class in action. http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html

Overlay Transparency should override Parent Background

I have a Overlay over my WPF Applicaiton, it shows some boarders as "context sensitive help". The boarders should now overrule the parent background and show the content behind (some kind of a view Port through the background).
The Controls look like this without Overlay:
With the Overlay Activated it looks like this:
The Overlay is a Usercontrol containing a ListBox of Items it should supply a boarder to.
The ListBoxPanel is a Canvas and the ListBoxItems are the Boarders(Buttons) you can see, which are moved over the UIElements they should surround using a ItemContainerStyle.
The Overlay looks like this:
<ItemsControl ItemsSource="{Binding HelpItems}" KeyboardNavigation.TabNavigation="Cycle" IsTabStop="True"
helpers:FocusHelper.FocusOnLoad="True" FocusVisualStyle="{StaticResource EmptyFocusVisual}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Button Command="{Binding ShowPopupCommand}" Background="Transparent" BorderThickness="2">
<Button.Style>
<Style>
<Setter Property="Button.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
<Setter Property="FrameworkElement.Width" Value="{Binding Width}" />
<Setter Property="FrameworkElement.Height" Value="{Binding Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
I want the overlay to be transparent inside the boarder, while keeping the semitransparent dimming background on the list box, in other words the empty space of my ListBox should be Gray.
Is there any easy way I can get the Light Blue border to show the Content behind the panel without the semi transparent background of my Overlay?
This is the target result:
I did as well try to create a Opacity filter but it is the wrong way around. And it does not seem there is a easy way to invert a opacity filter.
Console,
Ok we have "to make some holes in the ice".
So here is a custom control : OverlayWithGaps that draws itself, with a given background that can be semi transparent.
OverlayWithGaps has a Rect Collection that represents the gaps :
public ObservableCollection<Rect> Gaps
{
get { return (ObservableCollection<Rect>)GetValue(GapsProperty); }
set { SetValue(GapsProperty, value); }
}
private static FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
null
);
public static readonly DependencyProperty GapsProperty =
DependencyProperty.Register("Gaps", typeof(ObservableCollection<Rect>), typeof(OverlayWithGaps), fpm);
With AffectsRender, if that dependency property changes redrawing will happen.
Here is the drawing function :
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
if (Gaps != null && Gaps.Count > 0)
{
Geometry newGeometry = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
foreach (var gap in Gaps)
// remove each rectangle of the global clipping rectangle :
// we make "a hole in the ice"
newGeometry = Geometry.Combine(newGeometry,
new RectangleGeometry(gap),
GeometryCombineMode.Exclude,
transform: null);
// When the geometry is finished, we make the hole
dc.PushClip(newGeometry);
}
dc.DrawRectangle(Background, null, new Rect(0, 0, ActualWidth, ActualHeight));
}
EDIT
3. Rectangles are provided from the ItemsControl ListViewItems
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// overlay is the OverlayWithGaps instance
// in the window
overlay.Gaps = new ObservableCollection<Rect>(
itemsControl1.FindAllVisualDescendants()
.OfType<Grid>()
.Select(grid => {
Point relativePoint = grid.TransformToAncestor(this)
.Transform(new Point(0, 0));
return new Rect(relativePoint.X,
relativePoint.Y,
grid.ActualWidth,
grid.ActualHeight
);
})
);
}
Note that I select Grids in the LINQ query, because they are in the DataTemplate.
But the Linq query could select nearly anything (by name, ...)
The FindAllVisualDescendants() extension function can be found here :
Datagrid templatecolumn update source trigger explicit only updates first row
Here is the full working solution : http://1drv.ms/1OO2gWk
Best coding

How to create an array of labels in wpf?

I am trying to simulate LED indicators in my WPF App (around 16). I get 2 bytes from serial port, and based on the bits, I need to turn ON/OFF the LED indicators on my App window.
Example: 0xFF, 0xFE => all but the last LED are ON.
I am using labels with a dark background color to indicate an OFF LED, and a bright background color for an ON LED.
If I have an array of labels, then I could possibly do something like this:
for(i = 0; i < 16; i++)
{
if(bitArray[i] == true)
lblLED[i].Background = Brushes.Pink;
else
lblLED[i].Background = Brushes.Maroon;
}
Any suggestions on whats the best way to do this? A sample code which can show how this would work will be helpful. Thanks!
I'm sure you could figure out how to do what you're asking, but let's consider the tools at hand? You have an array of bool, it seems. As was suggested, an ItemsControl can handle them just wonderfully. First, let's do some code-behind to transform our bool's into brushes to set the background of our items.
using System;
using System.Windows.Media;
using System.Windows.Data;
using System.Globalization;
namespace MyNamespace
{
public class BoolToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// return a Pink SolidColorBrush if true, a Maroon if false
return (bool)value ? Brushes.Pink : Brushes.Maroon;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (SolidColorBrush)value == Brushes.Pink;
}
}
}
This will allow you to translate your bool[] bitArray into a series of brushes when bound to an ItemsControl. Now for some Xaml :
First, make sure you declare your local namespace (which contains the converter we just defined) in the xmlns attributes as well as the System Core Library (see the xmlns attributes).
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<!-- our local namespace -->
xmlns:my="clr-namespace:MyNamespace"
<!-- system core library -->
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="600" Width="900">
<Grid>
<ItemsControl Name="LEDPanel"> <!-- Need to Name this Control in order to set the ItemsSource Property at startup -->
<ItemsControl.Resources>
<my:BoolToBrushConverter x:Key="LEDConverter" /> <!-- Here we define our converter for use, note the preceding my: namespace declaration -->
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" /> <!-- this will make the items defined in the ItemTemplate appear in a row -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type sys:Boolean}"> <-- We will be binding our ItemsControl to a bool[] so each Item will be bound to a bool -->
<Border Margin="3" CornerRadius="10" Height="20" Width="20" BorderThickness="2" BorderBrush="Silver" Background="{Binding Converter={StaticResource LEDConverter}}" />
<!-- This is where we describe our item. I'm drawing a round silver border and then binding the Background to the item's DataContext (implicit) and converting the value using our defined BoolToBrushConverter -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Edit: I forgot the DataBinding. In your Window's constructor:
public MainWindow()
{
InitializeComponent();
LEDPanel.ItemsSource = bitArray;
}
The concepts covered are INotifyPropertyChanges (in .net 4.5 (minimal changes for other versions but same concept)), ItemsControl and finally style triggers.
INotifyPropertyChanges
My first step is to put INotifyPropertyChanges on our mainwindow to notify the WPF controls of any changes automatically. You will convert your bit array to a list and simply drop it in (maybe on a timer?) when needed. Note it doesn't matter how many there are...the control will expand.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private List<bool> _LedStates;
public List<bool> LedStates
{
get { return _LedStates; }
set
{
_LedStates = value;
NotifyPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
LedStates = new List<bool>() {true, false, true};
}
#region INotifyPropertyChanged
/// <summary>
/// Event raised when a property changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the property that has changed.</param>
protected virtual void NotifyPropertyChanged( [CallerMemberName] String propertyName = "" )
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler( this, new PropertyChangedEventArgs( propertyName ) );
}
}
#endregion
}
ItemsControl & Style Triggers
Then in the WPF xaml we will bind to the list of bools and use an item template (think a generic mini view for each data item found). Within that template we will show a color of red or green depending on the state of the boolean.
No converter needed because we setup a style trigger which is attune to a target value. If the Textblocks text is "True" we will show Green and when "False" we will trigger/show red.
<Window x:Class="WPFBindToArray.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl x:Name="icBitViewer"
ItemsSource="{Binding LedStates}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch"
IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Grid.Column="0">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="True">
<Setter Property="Background" Value="Green" />
</Trigger>
<Trigger Property="Text" Value="False">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
When this program is run here is the result:
Try this. It's clearly just an example, I advice you to use the features of WPF and maybe use a different control than a Label.
Func<ushort, bool[]> getBits = s =>
{
var bools = new bool[sizeof (ushort)*8];
for (var i = 0; i < bools.Length; i++)
bools[i] = (s & 1 << i) > 0;
return bools;
};
var bits = getBits(2);
var labels = new Label[sizeof (ushort)*8];
for (var i = 0; i < labels.Length; i++)
{
var label = new Label {Background = bits[i] ? Brushes.Green : Brushes.Red};
labels[i] = label;
}
//Do something with the Label array

Unable to autoresize columns in ListView/GridView using an attached property

I have the following Attached Property:-
public partial class GridViewProperties
{
public static readonly DependencyProperty DoAutoSizeColumnsProperty =
DependencyProperty.RegisterAttached("DoAutoSizeColumns",
typeof(bool),
typeof(GridViewProperties),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
DoAutoSizeColumnsChanged));
public static bool GetDoAutoSizeColumns(DependencyObject obj)
{
return (bool)obj.GetValue(DoAutoSizeColumnsProperty);
}
public static void SetDoAutoSizeColumns(DependencyObject obj, bool value)
{
obj.SetValue(DoAutoSizeColumnsProperty, value);
}
private static void DoAutoSizeColumnsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var gv = obj as GridView;
if (gv == null)
return;
if (e.NewValue != null && (bool)e.NewValue)
{
AutoSizeColumns(gv.Columns);
SetDoAutoSizeColumns(gv, false);
gv.InvalidateProperty(GridView.ColumnCollectionProperty);
}
}
private static void AutoSizeColumns(GridViewColumnCollection gvcc)
{
// same code as double clicking column gripper
foreach (var gvc in gvcc)
{
// if already set to auto, toggle it so that we can re-run width="auto"
//if (double.IsNaN(gvc.Width))
gvc.Width = gvc.ActualWidth;
// now do it
gvc.Width = double.NaN;
//gvc.InvalidateProperty(GridViewColumn.WidthProperty);
//gvc.ClearValue(GridViewColumn.WidthProperty);
}
}
}
I use it in XAML in the following fashion:
<Style x:Key="AutoColumnStyle" TargetType="ListView">
<Setter Property="View">
<Setter.Value>
<GridView infra:GridViewProperties.AutoSizeColumns="{Binding Path=DataContext.DoAutoSizeColumns, Source={x:Reference uc}}">
<GridViewColumn Width="auto" Header="Title" CellTemplate="{StaticResource Name}" />
<GridViewColumn Width="auto" Header="First" CellTemplate="{StaticResource First}"/>
<GridViewColumn Width="auto" Header="Last" CellTemplate="{StaticResource Last}"/>
</GridView>
</Setter.Value>
</Setter>
The abaove is in UserControl.Resources.
The rest of the XAML is:
<ListView ItemsSource="{Binding Names}" VerticalAlignment="Top" HorizontalAlignment="Left"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
SelectionMode="Single"
x:Name="ListViewContracts"
KeyboardNavigation.TabNavigation="Continue"
KeyboardNavigation.DirectionalNavigation="Cycle"
Style="{StaticResource AutoColumnStyle}"
>
<ListView.ItemContainerStyle >
<Style BasedOn="{StaticResource ListViewItemContainerStyle}" TargetType="ListViewItem">
<Setter Property="Focusable" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
(I have tried this with no Width="auto" too).
Whenever I set DoAutoSizeColumns = true in my ViewModel I see everything work as expected in the attached property except what it is desgined for that is the gridview columns are not resized according to the largest item in that column (even though I see gv.Width toggled from and to double.Nan which is how resize is meant to work).
As you can see I have tried a number of variations in the attached property mostly commented out including adding in all the FrameworkPoprertyMetadataOptions and trying various InvalidatePoperty attempts but also UIPropertyMetadata too (and DynamicResource too).
What am I missing?
UPDATE
This attached property works in other GridViews the only difference I can see here is that - I need to switch GridViews in the ListView which was not indicated above -but the key difference is that I inject as a style rather than directly. (On second thoughts this may not be the case since the firs item in a column in these other GridViews is always the largest item).
It was to do with the injection of a style.
I had two different GridViews switched by a boolean and used a DataTrigger to inject the alternate one and AutoSizeColumns worked fine on that altererate GridView! So I made both styles be injected by a DataTrigger rather having the default set by just a setter as in the XAML above.
As to why this is case I would be interested in finding out. At least I have a fully MVVM way of autosizing columns which I have not seen in other SO answers.
(BTW I have other attached properties that do not need me to manually trigger the autosize, I just wrote this version to identify the problem).

Categories

Resources