I have many very similar resources in xaml (varying by a tiny bit: name of property in bingings, static text in header, etc.) which are quite big and complex:
<Window.Resource>
<A x:Key="a1"> ... </A>
<A x:Key="a2"> ... </A>
...
<B x:Key="b1"> ... />
<B x:Key="b2"> ... />
...
<C x:Key="c1"> ... />
...
</Window.Resource>
And my aim is to have just this:
<A x:Key="a" ... />
<B x:Key="b" ... />
<C x:Key="c" ... />
...
where resource become kind of template. But then I need to somehow define a parameter to alter each such resource (e.g. to modify property name in the binding) before using it.
My current idea is to manipulate resources as text:
var xaml = #"... Text = ""{Binding %PROPERTY%}"" ...";
xaml = xaml.Replace("%PROPERTY%", realPropertyName);
view.Content = XamlReader.Parse(xaml)
But defining xaml strings in code-behind doesn't sounds good, they should be a part of xaml, where they are used.
So I had this brilliant idea:
// get some resource and restore xaml string for it, yay!
var xaml = XamlWriter.Save(FindResource("some resource"));
But unfortunately XamlWriter is very limited, it didn't worked, the restored this way xaml is totally unusable.
Then I had a thought to define resource as string:
<clr:String x:Key="a">...</clr:String>
But multiline string and special character in xaml making this approach looking very ugly. Don't try it at home.
Ideally I want to define resources as before (to have intellisence and stuff) and just want to modify them at run-time somehow, therefore my question.
The localized case of the problem (it's quite the same) is to have parameter in DataTemplate. I was asking question about dynamic columns earlier, that's why I have so many similar resources defined currently and trying to find a solution again.
I forgot to add a concrete example of resource as well as some form of MCVE:
<Window.Resources>
<GridViewColumn x:Key="column1">
<GridViewColumn.Header>
<DataTemplate>
<TextBlock Text="Header1" />
</DataTemplate>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value1}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
... more similar columns
...
</Window.Resources>
<ListView x:Name="listView" ItemsSource="{Binding Items}">
<ListView.View>
<GridView />
</ListView.View>
</ListView>
some columns are added
var view = (GridView)listView.View;
foreach(var column in Columns.Where(o => o.Show))
view.Columns.Add((GridViewColumn)FindResouce(column.Key));
where Columns collection defines which columns can be shown, which are hidden, their width, etc.
public class Column
{
public string Key { get; set; } // e.g. "column1"
public bool Show { get; set; }
...
}
To have 100 columns I have to define 100 "columnX" resources, but they are very similar. My challenge is to define just one and then somehow alter dynamic parts (in this case to change "Header1" to "Header2" and "Value1" to "Value2").
I have found a way to write xaml which:
has designer support
has intellisense support;
can be modified at run-time.
For this xaml (resources) needs to be put into separate ResourceDictionary which Build property set to Embedded Resource
The content will looks like this:
<ResourceDictionary ...>
<GridViewColumn x:Key="test" Header="%HEADER%"> <!-- placeholder for header -->
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding %CELL%}" /> <!-- placeholder for cell property name -->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ResourceDictionary>
And the code to load and modify
// get resource stream
var element = XElement.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("..."));
// get xaml as text for a specified x:Key
var xaml = element.Descendants().First(o => o.Attributes().Any(attribute => attribute.Name.LocalName == "Key")).ToString();
// dynamic part
xaml = xaml.Replace("%HEADER%", "Some header");
xaml = xaml.Replace("%CELL%", "SomePropertyName");
// xaml to object
var context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
var column = (GridViewColumn)XamlReader.Parse(xaml, context);
view.Columns.Add(column);
I personally don't use designer at all (only to quickly navigating) and write all xaml with hands. Having no designer support is not a problem for me.
Intellisense support and seeing mistakes at compile time are very handy. Disregards of build action the xaml will be fully validated, which is a good thing (compared to saving xaml in string in code behind or in a text-file).
Separating resources from window/user control are sometimes problematic, e.g. if there are bindings with ElementName or references to other resources (which are not moved to resource dictionary), etc. I have currently issue with BindingProxy, therefore this solution is not a final one.
More focusing on your GridView example instead of the question title.
You could create a UserControl or Custom Control in order to define the appearence of a cell content.
Within the custom control, you can define your whole shared styling and define dependency properties for things that should be different per cell.
As an example, here is a custom control MyCellContent that allows to bind a Text property or to bind a MyTextPropertyName property which will automatically create a binding on the Text property, redirecting to whatever MyTextPropertyName specifies:
public class MyCellContent : Control
{
static MyCellContent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCellContent), new FrameworkPropertyMetadata(typeof(MyCellContent)));
}
// Specify a property name that should be used as binding path for Text
public string MyTextPropertyName
{
get { return (string)GetValue(MyTextPropertyNameProperty); }
set { SetValue(MyTextPropertyNameProperty, value); }
}
public static readonly DependencyProperty MyTextPropertyNameProperty =
DependencyProperty.Register("MyTextPropertyName", typeof(string), typeof(MyCellContent), new PropertyMetadata(null, OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindingOperations.SetBinding(d, TextProperty, new Binding(e.NewValue as string));
}
// The text to be displayed
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyCellContent), new PropertyMetadata(null));
}
And in Themes/Generic.xaml
<Style TargetType="{x:Type local:MyCellContent}">
<!-- Demonstrate the power of custom styling -->
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCellContent}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, this is something to build upon.
For example, you could create a CellTemplateSelector that creates a cell template, where the Text property binding is determined by another property value of the data item:
// Specialized template selector for MyGenericData items
public class GridViewColumnCellTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var data = item as MyGenericData;
return CreateCellTemplate(data.MyTargetPropertyName);
}
/// <summary>
/// Create a template with specified binding path
/// </summary>
private DataTemplate CreateCellTemplate(string targetPropertyName)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetValue(MyCellContent.MyTextPropertyNameProperty, targetPropertyName);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
Another different way to use the MyCellContent control would be a customized MyGridviewColumn, that does basically the same as the template selector above, but instead of a data driven property selection, it allows to specify a binding to be used on the Text property:
/// <summary>
/// Pre-Templated version of the GridViewColumn
/// </summary>
public class MyGridviewColumn : GridViewColumn
{
private BindingBase _textBinding;
public BindingBase TextBinding
{
get { return _textBinding; }
set
{
if (_textBinding != value)
{
_textBinding = value;
CellTemplate = CreateCellTemplate(value);
}
}
}
/// <summary>
/// Create a template with specified binding
/// </summary>
private DataTemplate CreateCellTemplate(BindingBase contentBinding)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetBinding(MyCellContent.TextProperty, contentBinding);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
Usage example with some test data:
<Window.Resources>
<x:Array x:Key="testItems" Type="{x:Type local:MyGenericData}">
<local:MyGenericData Property1="Value 1" Property2="Value 3" MyTargetPropertyName="Property1"/>
<local:MyGenericData Property1="Value 2" Property2="Value 4" MyTargetPropertyName="Property2"/>
</x:Array>
<local:GridViewColumnCellTemplateSelector x:Key="cellTemplateSelector"/>
</Window.Resources>
...
<ListView ItemsSource="{Binding Source={StaticResource testItems}}">
<ListView.View>
<GridView>
<GridViewColumn CellTemplateSelector="{StaticResource cellTemplateSelector}" Header="ABC" Width="100" />
<local:MyGridviewColumn TextBinding="{Binding Property2}" Header="DEF" Width="100" />
</GridView>
</ListView.View>
</ListView>
The result:
A gridview where the first column displays the values "Value 1" and "Value 4", because it selects the value from "Property1" in the first row and from "Property2" in the second row. So the displayed data is driven by two data dimensions: the specified property name and the target property value.
The second column displays the values "Value 3" and "Value 4", because it utilizes the specified binding expression "{Binding Property2}". So the displayed data is driven by the specified binding expression, which could refer to a data property or anything else that's legally binding within a data grid cell.
Related
I have created a custom control, which inherits TextBox. It basically has an extra property 'Seconds' and set a binding on 'Text', to shown the 'Seconds' formatted, as eg. 2m 5s, using a converter.
I now want to default right-align the text.
From other custom controls I know we sometimes will want to set/override values using styles. If I set the value directly in the constructor I will not be able to do this.
I would usually something like this:
TextAlignmentProperty.OverrideMetadata(typeof(DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
But this does NOT appear to work:
First two have the default alignment, then they are Left, Center and Right aligned directly on the controls. Seconds row has a style setting alignment to Center
I have bound a TextBlock to the TextAlignment of the first DurationTextBox, and this states that the aligment is 'Right', but this is not how it is shown!
Can anyone explain:
A. Why this is not working?
B. How to do this correctly, or something with the same end effect? (default aligned Right, but possible to override from Style)
C# class :
Please note that this is a simplified version. The complete one has Min, Max, option of confirming value changed and option for out of range action, which is the reason for the structure of the class. Please keep focus on the TextAlignment issue!
(The SecondsToDurationStringConverter and DurationStringValidator can be removed to make the example compile with the same effect)
public class DurationTextBox : TextBox
{
#region Dependency properties
/// <summary>
/// Property for <see cref="Seconds"/>
/// </summary>
[NotNull] public static readonly DependencyProperty SecondsProperty = DependencyProperty.Register(nameof(Seconds), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), SecondsChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Seconds to show as duration string
/// </summary>
public double Seconds
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(SecondsProperty); }
set { SetValue(SecondsProperty, value); }
}
/// <summary>
/// Property for <see cref="EditValue"/>
/// </summary>
[NotNull] public static readonly DependencyProperty EditValueProperty = DependencyProperty.Register(nameof(EditValue), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), EditValueChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Number being edited by the actual text box. Transferred to <see cref="Seconds"/>.
/// <para>Do NOT bind to this property from outside this control</para>
/// </summary>
public double EditValue
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(EditValueProperty); }
set { SetValue(EditValueProperty, value); }
}
#endregion Dependency properties
private bool _isLocked;
static DurationTextBox()
{
// TextAlignment
TextAlignmentProperty.OverrideMetadata(typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
}
/// <inheritdoc />
public DurationTextBox()
{
SecondsToDurationStringConverter secondsToDurationStringConverter = new SecondsToDurationStringConverter();
// Text
Binding binding = new Binding(nameof(EditValue)) { Source = this, Converter = secondsToDurationStringConverter, NotifyOnValidationError = true };
binding.ValidationRules.Add(new DurationStringValidation());
SetBinding(TextProperty, binding);
}
private static void SecondsChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(EditValueProperty, durationTextBox.Seconds);
durationTextBox._isLocked = false;
}
}
private static void EditValueChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(SecondsProperty, durationTextBox.EditValue);
durationTextBox._isLocked = false;
}
}
}
XAML code:
<Label Content="Demo.DurationTextBox" FontWeight="Bold"/>
<WrapPanel>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" x:Name="DemoDurationTextBox"/>
<TextBlock Text="{Binding ElementName=DemoDurationTextBox, Path=TextAlignment}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right"/>
</WrapPanel>
<WrapPanel>
<WrapPanel.Resources>
<Style TargetType="demo:DurationTextBox">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</WrapPanel.Resources>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right" />
</WrapPanel>
A: This isn't working because TextBox.TextAlignment is inherited, so by default the active value is determined by the parent control at runtime. It doesn't make sense for a class to override the default value of the static DependencyProperty object itself.
The fact that the debug binding you added displays "Right" is strange, and probably an internal WPF optimisation glitch.
B: The correct solution is to set TextBox.TextAlignment="Right" (note the type name qualifier) on a parent control, e.g. your wrap panel. That value will then be automatically applied to all child text blocks unless they or an intermediate parent override it further, including via a style.
C: I would add that the code you posted seems to be an attempt to re-invent DataGrid. That control supports transactional editing out of the box and might save you a lot of time if you switch to it!
While it is not the solution I would have preferred, I have found a way that allows me to have the default alignment of my control to Right, while being able to overwrite it locally by using a style, or directly on the control, without affecting other TextBoxes
I made a default style:
<Style TargetType="controls:DurationTextBox">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
As I have other resource dictionaries which must be included anyway this will work in my situation.
I would have preferred simply setting the default value of my control, but according to Artfunkel that is sadly not possible.
The issue with my chosen approach of using a default style is if a different default style is set for TextBox in a project, the DurationTextBox will not use/inherit this style, because it has its own default style, thus someone using the library has to also set a similar style/override the style for the DurationTextBox for them not to appear different.
If the DurationTextBox had not needed a different default style, containing the text alignment, it would have been possible to have the default style be the same as a TextBox, but this is not a possibility now.
As we do have a different default style for TextBox I have added a BasedOn to my default style:
<Style TargetType="controls:DurationTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
I have a custom control in my application. One of the dependency properties is an ObservableCollection<ToggleButton>:
public ObservableCollection<ToggleButton> HeaderButtons
{
get { return (ObservableCollection<ToggleButton>)GetValue(HeaderButtonsProperty); }
set { SetValue(HeaderButtonsProperty, value); }
}
public static readonly DependencyProperty HeaderButtonsProperty = DependencyProperty.Register("HeaderButtons", typeof(ObservableCollection<ToggleButton>), typeof(Expandable), new PropertyMetadata(new ObservableCollection<ToggleButton>()));
I'm then putting them in a ListView in Generic.xaml:
<ListView ItemsSource="{TemplateBinding HeaderButtons}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
...and using it like this:
<controls:MyControl.HeaderButtons>
<ToggleButton x:Name="FilterButton">
<Image Source="/Assets/Icons/Empty Filter-512.png" Height="15" Width="15"/>
</ToggleButton>
</controls:MyControl.HeaderButtons>
However, I keep ending up with a duplicate item:
I can't figure out how that item is getting there. I can fix it by removing my custom ListView.ItemsPanel, but of course that makes my items flow vertically, defeating the entire purpose. Can anyone else see why this would be duplicating the item?
EDIT: For further interest, if I go into the Live Visual Tree I can see that both buttons have the name "FilterButton". Which should, of course, not be possible.
EDIT: Here's the ContentPresenter from the MainWindow:
<ContentPresenter Content="{Binding CurrentControl, Mode=OneWay}" Grid.Row="1" Grid.Column="1"/>
And CurrentControl is set to an instance of my UserControl:
private UserControl currentControl;
public UserControl CurrentControl
{
get { return currentControl; }
set
{
if (currentControl != value)
{
currentControl = value;
OnPropertyChanged("CurrentControl");
}
}
}
The source of the problem is the default value of your HeaderButtonsProperty - you set one using new PropertyMetadata(new ObservableCollection<ToggleButton>()). Contrary to what you expect it does not create one instance of the collection for each instance of your control, but a single instance shared across all of your controls.
Then you use this XAML syntax:
<controls:MyControl.HeaderButtons>
<ToggleButton (...) />
</controls:MyControl.HeaderButtons>
which does not assign a new collection to your HeadersButton property, but rather adds the specified item to the existing one. So each time this part of code is "executed", it adds a new copy of the ToggleButton to the single collection shared by all your controls.
To resolve the problem you should remove the default value from your HeaderButtonsProperty's metadata and assign a new collection instance in your control's constructor - that way each control instance will have it's own independent collection.
What I am attempting to do is have a collection of items shown in a GridView control and have the size of these items change based on a command executed by a separate button.
For example, having a row of buttons across the top reading “Small”, “Medium” and “Large” and having the items in the GridView respond to the relevant command by displaying its items in the relevant state.
I have the gridview declared like so
<GridView ItemsSource="{Binding Squares}"
With Squares being an observable collection of Square objects that have a Title and a Fill property.
At first I went down the DataTemplateSelector route by declaring the following data templates in the Resources section of the page.
<DataTemplate x:Key="SquareSmallTemplate">
<Grid Height="100" Width="100">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareMediumTemplate">
<Grid Height="150" Width="150">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareLargeTemplate">
<Grid Height="200" Width="200">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
The idea being that the grid’s height and width properties are different for the relevant template. I declared the following data templates in the selector
public DataTemplate SmallTemplate { get; set; }
public DataTemplate MediumTemplate { get; set; }
public DataTemplate LargeTemplate { get; set; }
And in the SelecteTemplateCore method I just returned the relevant template
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
string value = item as string;
if (value != null)
{
if (value == "Small")
return SmallTemplate;
else if (value == "Medium")
return MediumTemplate;
else if (value == "Large")
return LargeTemplate;
return base.SelectTemplate(item, container);
}
else
{
return base.SelectTemplateCore(item, container);
}
}
However, with this method (and, by design of the DataTemplateSelector) the object being passed in is the item in the collection (the Square).
This is fine if I wanted each item to have a different appearance or something, but what I need is the template to change based on another property on the view model.
For this, I have the following
public string State {get; set;}
and this is set to “Small”, “Medium, or “Large based on a separate row of three buttons that execute a command that sets this property to the relevant value.
How do I relate the State property to changing to the relevant DataTemplate?
Another route I tried was to have a single Data template that used the VSM to animate the Height/Width properties in the relevant states. However I could not get the relevant animation to execute when the State changed.
Any help would be great, thanks
There are a few ways to do this, I'm not sure which would be best. In any case, you'll need 1) a trigger, and 2) the action to update the template. I am leaning towards using PropertyChangedTrigger along with an InvokeCommandAction.
<GridView x:Name="grid">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding State}">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=UpdateTemplateCommand}" CommandParameter="{Binding State}" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<GridView>
(Here the "AncestorType" would just be the root of the view, so please change "UserControl" as needed.)
Then in the view, you would have an ICommand that updates the template:
UpdateTemplateCommand = new DelegateCommand(state => {
switch ((string)state)
{
default:
case "Small" : grid.ItemTemplate = "SquareSmallTemplate"; break;
case "Medium" : grid.ItemTemplate = "SquareMediumTemplate"; break;
case "Large" : grid.ItemTemplate = "SquareLargeTemplate"; break;
}
});
IDK ... after writing this out it seems a bit convoluted. Maybe you'd find it preferable to add a CurrentDataTemplate property to the view-model, and assign it by creating DataTemplates from strings using XamlReader.
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
Let's say I have a Book app with the following arrangement:
There are a few page types
Regular
With picture
Only picture
etc...
Each of this pages can have many bookmarks. In order to display everything correctly I have created a datatemplate selector like this
Page.xaml
<Pages:PageTypeSelector Content="{Binding}">
<Pages:PageTypeSelector.Regular>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<!-- Show regular content here-->
<Bookmark:BookmarkView />
</Grid>
</DataTemplate>
</Pages:PageTypeSelector.Regular>
<Pages:PageTypeSelector.WithPicture>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<!-- Show mixed content here-->
<Bookmark:BookmarkView />
</Grid>
</DataTemplate>
</Pages:PageTypeSelector.WithPicture>
<Pages:PageTypeSelector.OnlyPicture>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<!-- Show picture content here-->
<Bookmark:BookmarkView />
</Grid>
</DataTemplate>
</Pages:PageTypeSelector.WithPicture>
</Pages:PageTypeSelector>
How the bookmark view looks like isn't important now (it just displays the bookmarks for each page)
The problem is I need somehow to set it's PageId property so that the bookmark control can get the bookmarks from a service.
I have added a property to the BookmarkView.xmal.cs
public Guid PageId
{
get { return _pageId; }
set
{
_pageId= value;
_viewModel.PageId= value; // Here I wanted to pass the same value to the
// viewModel which would doo the job then
}
}
I have then tried to do something like this
<Bookmark:BookmarkView PageId={Binding PageId, Mode=TwoWay}/>
Intellisense was suggesting PageId while I was typing but nothing is happening.
How can I pass the PageId to the view?
Edit: Changing to
public object PageId
{
get { return _pageId; }
set
{
_pageId= value;
}
}
Shows me that the value is of type System.Windows.Data.Binding. How can I now get the value?
Create a dependency property for PageID.