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
Related
I've been searching for a while and I just cannot find what I'm doing wrong.
I have a list of names that I show in a View, in my View I created an itemsControl, the ItemsSource is set to the observableCollection in the ViewModel. The goal is to give a overview of available names in an nxn table. The user should be able to filter the results with the searchbox on top.
First I tried this using a List of Lists which did not work as the view was not being updated according to the string in the searchbox. I found that I should actually use a ObservableCollection because it implements INotifyCollectionChanged. Below I try to implement an ObservableCollection but I fail to update the view when this collection changes.
View (see ItemsControl in the Xaml section):
Xaml Resources
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
<Button Content="{Binding}" Height="150" Width="150" Margin="20,20,20,20">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#2ED99A"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#2EB9D9"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#333f4a"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
Xaml
<Grid Margin="10,10,10,10" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{Binding HcfOverviewModel.NSharedModules, StringFormat=Shared Circuits: {0}}"></TextBlock>
<TextBlock Margin="30,0,0,30" Text="{Binding HcfOverviewModel.NPrivateModules, StringFormat=Private Circuits: {0}}"></TextBlock>
</StackPanel>
<Button Grid.Column="1" VerticalAlignment="Top" MaxHeight="20" Content="See Private Circuits"></Button>
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Hidden">
<ItemsControl HorizontalAlignment="Center" VerticalAlignment="Top" ItemsSource="{Binding Modules, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{DynamicResource DataTemplate_Level1}"/>
</ScrollViewer>
</Grid>
ViewModel:
class HcfOverviewViewModel:BaseViewModel
{
private HcfOverviewModel _hcfOverviewModel;
private string _pathToSharedModules;
//private List<List<string>> _modules;
private ObservableCollection<ObservableCollection<string>> _modules;
public HcfOverviewViewModel()
{
_pathToSharedModules = #"C:\Users\scamphyn\source\repos\TSD\TSD\TempTestFolder";
_hcfOverviewModel = new HcfOverviewModel();
_hcfOverviewModel.NSharedModules = CheckDirectory.FindModules(_pathToSharedModules).Length;
_hcfOverviewModel.SharedModuleNames = CleanModuleNames(CheckDirectory.FindModules(_pathToSharedModules));
//_modules = CreateGridLayout(_hcfOverviewModel.SharedModuleNames);
_modules = CreateGrid(_hcfOverviewModel.SharedModuleNames);
}
public HcfOverviewModel HcfOverviewModel
{
get => _hcfOverviewModel;
}
public void Filter(string searchString)
{
Console.WriteLine(searchString);
if (searchString == "")
{
//Modules = CreateGridLayout(_hcfOverviewModel.SharedModuleNames);
Modules = CreateGrid(_hcfOverviewModel.SharedModuleNames);
}
else
{
Modules.Clear();
string[] filteredNames = _hcfOverviewModel.SharedModuleNames.Where(n => n.Contains(searchString)).ToArray();
//Modules = CreateGridLayout(filteredNames);
Modules = CreateGrid(filteredNames);
}
}
private string[] CleanModuleNames(string[] listToClean)
{
List<string> moduleNames = new List<string>();
foreach (string s in listToClean)
{
string[] listPath = s.Split('\\');
string name = listPath[listPath.Length - 1];
moduleNames.Add(name);
}
return moduleNames.ToArray();
}
public ObservableCollection<ObservableCollection<string>> CreateGrid(string[] moduleNames)
{
ObservableCollection<ObservableCollection<string>> lsts = new ObservableCollection<ObservableCollection<string>>();
int circuitsPerRow = 4;
int _nRows = moduleNames.Length / circuitsPerRow;
Queue<string> modules = new Queue<string>(moduleNames);
for(int i = 0; i <= _nRows; i++)
{
lsts.Add(new ObservableCollection<string>());
for (int j = 0; j < circuitsPerRow; j++)
{
if (modules.Count != 0)
{
lsts[i].Add(modules.Dequeue());
}
else
{
break;
}
}
}
return lsts;
}
public List<List<string>> CreateGridLayout(string[] moduleNames)
//To Create the grid in HcfOverview
{
List<List<string>> lsts = new List<List<string>>(); // create list of list to store nxn matrix data in.
int circuitsPerRow = 4;
//Determine correct size nxn
int _nRows = moduleNames.Length / circuitsPerRow;
//Create Queue
Queue<string> modules = new Queue<string>(moduleNames);
//Create data for ItemControls
for (int i = 0; i <= _nRows; i++) // number of rows
{
lsts.Add(new List<string>());
for (int j = 0; j < circuitsPerRow; j++) // number of columns
{
if (modules.Count != 0)
{
lsts[i].Add(modules.Dequeue());
}
else {
break;
}
}
}
return lsts;
}
public ObservableCollection<ObservableCollection<string>> Modules
{
get => _modules;
set {
_modules = value;
OnPropertyChanged("Modules");
}
}
}
"_modules" which is my ObservableCollection is populated in the CreateGrid Method.
Here is what it looks like in the window:
The search box is in the "parent" View/ViewModel when I detect an update there I call the filter method in my ViewModel shown above to update the ObservableCollection.
How can this be resoloved?
Update:
Because I've been stuck on this issue for week now I decided to create a new "Project" and check if I could get it to work in a simplified project.
In this simplified project I managed to add and remove rows. I used the exact same methods as I was using before. So the binding and updating of my observablecollection is done right. I now wonder if this issue would be in how I load this UserControl. The UserControl displaying this data is loaded through another View. see code below:
<ContentControl Grid.Row="2" Content="{Binding CurrentHcfViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type ViewModels:HcfOverviewViewModel}">
<views:UserControlHcfOverview/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
My HcfOverviewViewModel is loaded into a usercontrol that exists in a different view. Could this have influence on the updating of the ObeservableCollection and its View in the HcfOverviewViewModel?
I found that I should actually use a ObservableCollection because it implements INotifyCollectionChanged.
Due to how you do your update its not working as you believe. When one changes the reference from one ObservableCollection to another ObservableCollection, that does not send a INotifyCollectionChanged message. Only add/delete/clear calls generate that message on an active referenced collection.
When you reset the reference you are telling the ItemsControl to fully clear itself and then show what is being cleared.
You need to not change the initial Modules collection after it is created. There are other issues with your logic that cascade from that and unless someone rewrites your custom control/logic, this question cannot be fully answered.
Regardless you need to start with not changing the references and then work on the changing internal collection items in the same pattern to the parent ObservableCollection.
Note, some controls need a Null to be set when one is setting to a new list. Its unclear if that is also compounding the issue, for you immediately change to the newer reference and the new reference top level does not seem to change the whole ItemsControl.
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.
I have an application that uses two separate projects. One is for the main executable which contains my ViewModels and the other is to control the theme/style of the application.
In the theme project, I have customized the DataGridColumnHeader's Style to include a CheckBox. Now how do I databind the CheckBoxes to my ViewModel?
My theme xaml
<Style x:Key='PlottableFilteringColumnHeaderStyle' TargetType='{x:Type primitives:DataGridColumnHeader}'>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type primitives:DataGridColumnHeader}">
<Grid>
<themes:DataGridHeaderBorder x:Name='HeaderBorder'>
<Grid x:Name="GridColumnHeader">
<StackPanel x:Name="argStackPanel">
<CheckBox x:Name="argCheckBox" Content="Enable Arg" Style="{DynamicResource ResourceKey=DefaultCheckBox}" />
</StackPanel>
</Grid>
</themes:DataGridHeaderBorder>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am then using MultiBinding for the argCheckBox
public class HeaderArgConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string headerText = values[0] as string;
if (!String.IsNullOrWhiteSpace(headerText))
{
FrameworkElement targetElement = values[1] as FrameworkElement;
DataGridColumnHeader header = targetElement.TemplatedParent as DataGridColumnHeader;
string columnName = header.DataContext != null ? header.DataContext.ToString() : "";
var argNumber = System.Text.RegularExpressions.Regex.Match(columnName.Split(':')[0], #"\d+$").Value; // use the header text to determine which arg number
Binding binding = new Binding("SelectedViewModel.EnableArg" + argNumber);
binding.Source = Window.DataContextProperty; // This is what I am unsure about
(targetElement as CheckBox).SetBinding(CheckBox.IsCheckedProperty, binding);
}
}
}
I keep getting 'BindingExpression path error: property not found on 'object'' error. Any ideas on how to fix this or if there is a better way to do this?
<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.
This is the code to reproduce this issue:
xaml:
<Window x:Class="WpfApplication1.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>
<TextBox Text="{Binding Num, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</Grid>
C#:
using System.ComponentModel;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Entity();
}
}
public class Entity : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double num;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public double Num
{
get { return num; }
set
{
num = value;
if (value > 100)
{
num = 100;
}
OnPropertyChanged("Num");
}
}
}
}
Now, if I run it, input 1, it's ok. Then input another 1, which makes it 11, it's still ok.
And then input another more 1, which makes 111, now the validation will work and change the value to 100, and the UI will show 100.
But then if I input more numbers, the UI will not change, it'll be 1001.
I guess it has something to do with setting the property to the same value(100) for twice.
But I have no idea how to fix it, by fix it, I mean to make the UI always follow the property value.
Thanks
<Window x:Class="WpfApplication1.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">
<Window.Resources>
<!--The Too tip for the textbox-->
<Style x:Key="txterror" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"></Setter>
<Setter Property="Background" Value="Red"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox x:Name="txt" Text="{Binding Mode=TwoWay, IsAsync=True, Path=Num, UpdateSourceTrigger=PropertyChanged}" Margin="63,36,71,184" >
<TextBox.Effect>
<DropShadowEffect ShadowDepth="5"/>
</TextBox.Effect>
</TextBox>
<TextBox Margin="63,96,71,124" Text="{Binding ElementName=txt,Path=Text}">
<TextBox.Effect>
<DropShadowEffect ShadowDepth="5" />
</TextBox.Effect>
</TextBox>
</Grid>
</Window>
Use above code its working fine logicaly.
means that when u set value of Num proeprty it setting proerty value to 100 but the textbox values of your widnow is not changed.
it is because of the framework element it uses the Error validation so what so ever u want to put into textbox it will type that and present that value on the textbox. but the property values is always 100 when ever its goes higher then 100.
Your Solution is to use the IsAsync=True in you binding statement
actually it should work as long as your input in your textbox can automatically converted to double. did you check your output window for bindingexceptions? if you debug with values like 1001 what happens in your setter?
Introduce property changed notification before truncating num to 100 and one immediately after truncating...
like this...
num = value;
OnPropertyChanged("Num");
if (value > 100)
{
num = 100;
OnPropertyChanged("Num");
}
Let me if this helps.