Set Name of and access individual CustomControls in ItemsControl - wpf

I am writing a programme with C#, .NET 4.6 and WPF. I would like to have a set of CustomControls arranged in a two-dimensional grid (size dynamically specified at runtime) and be able to access each CustomControl.
I did some research, found different pieces of information about the ItemsControl, and created a solution which to some extend does what I want.
Here are the relevant parts of the code, they compile and run.
XAML for CustomControl
<UserControl x:Class="TestApp.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Rectangle Fill="{Binding MyFill1, RelativeSource={RelativeSource FindAncestor, AncestorType=local:MyUserControl}}">
</Rectangle>
<Viewbox>
<TextBlock Text="{Binding MyText1, RelativeSource={RelativeSource FindAncestor, AncestorType=local:MyUserControl}}" >
</TextBlock>
</Viewbox>
</Grid>
</UserControl>
code-behind for CustomControl
namespace TestApp
{
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty MyText1Property =
DependencyProperty.Register("MyText1",
typeof(String), typeof(MyUserControl),
new PropertyMetadata(""));
public String MyText1
{
get { return (String)GetValue(MyText1Property); }
set { SetValue(MyText1Property, value); }
}
public static readonly DependencyProperty MyFill1Property =
DependencyProperty.Register("MyFill1",
typeof(SolidColorBrush),
typeof(MyUserControl),
new PropertyMetadata(new SolidColorBrush(Colors.Green)));
public SolidColorBrush MyFill1
{
get { return (SolidColorBrush)GetValue(MyFill1Property); }
set { SetValue(MyFill1Property, value); }
}
public MyUserControl()
{
InitializeComponent();
}
}
}
XAML for hosting MainWindow
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestApp"
mc:Ignorable="d"
Name="MyMainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl Name="MyItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding ElementName=MyMainWindow, Path=UniformGridColumns, Mode=OneWay}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyUserControl MyText1="{Binding Text1}" MyFill1="{Binding Fill1}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
code-behind for hosting main window
namespace TestApp
{
public partial class MainWindow : Window
{
public int UniformGridColumns //number of columns of the grid
{
get { return (int)GetValue(UniformGridColumnsProperty); }
set { SetValue(UniformGridColumnsProperty, value); }
}
public static readonly DependencyProperty UniformGridColumnsProperty =
DependencyProperty.Register("UniformGridColumns", typeof(int), typeof(MainWindow),
new FrameworkPropertyMetadata((int)0));
public MainWindow()
{
InitializeComponent();
//this.DataContext = this;
Setup(13, 5); //13 columns, 5 rows
}
public void Setup(int columns, int rows) //setup the grid
{
UniformGridColumns = columns;
SingleControl[] singleControls = new SingleControl[rows*columns];
for (int i = 0; i < rows*columns; i++)
singleControls[i] = new SingleControl()
{
Text1 = (i/ columns + 1) + ", " + (i % columns + 1),
Fill1 = new SolidColorBrush((i % 2 != 0) ? Colors.Yellow : Colors.Red)
}; //example, display position in grid and fill with two different colours
MyItemsControl.ItemsSource = singleControls.ToList<SingleControl>();
}
public MyUserControl GetSingleControl(int column, int row) //access a single control
{
//some code involving VisualTreeHelper
return null;
}
private class SingleControl //helper class for setting up the grid
{
public String Text1 { get; set; }
public Brush Fill1 { get; set; }
}
}
}
The method MainWindow.Setup(int, int) fills the ItemControl with the desired number of MyCustomControls, I can label and fill them with any colour I want.
Question 1:
How can I implement GetSingleControl(int, int) that returns the MyCustomControl on the specified position? I started with a solution involving VisualTreeHelper which seems to be clumsy and unflexible.
Question 2:
How can I set Name of all MyCustomControls, e.g. something like "MyCustomControl_01_05" for the item in row 1 and column 5.
Question 3:
If questions 1 and 2 cannot be answered on the basis of my solution, what would be a more suitable approach?
Thank you!

To give an example of what both elgonzo and Andy said, you should change things to be more MVVM friendly. Once you do more research you will understand why you dont want to bother with the DependencyProperties, binding to the code behind, and manually coding all the additions of the usercontrols.
This could be made pretty or more streamlined, but i coded it to give a full example of how this could be done with MVVM. I tried to make it simple and basic, while demonstrating how to refactor your idea.
New MainWindow.xaml
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestApp"
d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}}"
mc:Ignorable="d"
Name="MyMainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl Name="MyItemsControl" ItemsSource="{Binding MyList}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding GridRow}" />
<Setter Property="Grid.Column" Value="{Binding GridColumn}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding ColumnCount}" Rows="{Binding RowCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Fill="{Binding Fill1}"/>
<TextBlock Text="{Binding Text1}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
New MainWindow.xaml.cs (Notice there is no extra code)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Add a file MainWindowViewModel.cs:
-note the MyElement could be abstracted to a viewmodel for a UserControl if you desire.
public class MyElement : INotifyPropertyChanged
{
public MyElement()
{
//some default data for design testing
Text1 = "Test";
Fill1 = new SolidColorBrush(Colors.Red);
GridColumn = 13;
GridRow = 5;
}
private string _text1;
public string Text1
{
get { return _text1; }
set{
if (value != _text1) { _text1 = value; RaisePropertyChanged(); }
}
}
private Brush _fill1;
public Brush Fill1
{
get { return _fill1; }
set
{
if (value != _fill1) { _fill1 = value; RaisePropertyChanged(); }
}
}
private int _gridRow;
public int GridRow
{
get { return _gridRow; }
set
{
if (value != _gridRow) { _gridRow = value; RaisePropertyChanged(); }
}
}
private int _gridColumn;
public int GridColumn
{
get { return _gridColumn; }
set
{
if (value != _gridColumn) { _gridColumn = value; RaisePropertyChanged(); }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel() : this(13, 5) { }
public MainWindowViewModel(int columns, int rows)
{
ColumnCount = columns;
RowCount = rows;
MyList = new ObservableCollection<MyElement>();
//your original setup code
for (int i = 0; i < columns; i++)
{
for (int j = 0; j < rows; j++)
{
var vm = new MyElement
{
Text1 = (i / columns + 1) + ", " + (i % columns + 1),
Fill1 = new SolidColorBrush((i % 2 != 0) ? Colors.Yellow : Colors.Red),
GridColumn = i,
GridRow = j
};
MyList.Add(vm);
}
}
}
private int _rowCount;
public int RowCount
{
get { return _rowCount; }
set
{
if (value != _rowCount) { _rowCount = value; RaisePropertyChanged(); }
}
}
private int _columnCount;
public int ColumnCount
{
get { return _columnCount; }
set
{
if (value != _columnCount) { _columnCount = value; RaisePropertyChanged(); }
}
}
public ObservableCollection<MyElement> MyList { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I did a more full solution where it uses INotifyPropertyChanged. I wont explain the reason for using it (in the event you are unaware), as there are much better explanations you can quickly search for.
I also made it so all the dynamic information uses Binding to make things easier to change. Now the Grid size, and item positioning are bound to your data. So it should adjust automatically as you change your "MyElement"
This should give you a good starting point for refactoring your code, and help you utilize what WPF was designed to do, as there are many mechanisms built in so you dont have to hard code UI layer manipulation (as you were in the code behind)
This also answers your Questions:
Q1 : You can now just access to the List of MyElements and change them accordingly. The UI layer should update automatically when you change anything.
Q2 : You shouldnt need to do this now, as each MyElement will keep a property for it's Grid Position. Thus you can just access that.

Related

why is my ItemsControl displaying all the items on top of each other

I have an ItemsControl that is displaying all the items on top of each other. The default ItemsPanelTemplate is a StackPanel with a vertical orientation so I don't see why the items are not spread out vertically.
This example has a window which contains a ContentControl. This control is bound to a property called ElementColl which is found in the Resources class. The Resources class is set as the DataContext of the window.
The ElementColl property is of the type Elements. The Elements class contains a property of the type ObservableCollection. The Element object has a Number property and a SomeText property.
The constructor of the Window creates three Element instances and puts them into the collection.
The image at the end shows all three Elements being displayed on top of each other.
<Window x:Class="POC_ObservableCollectionInListbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:POC_ObservableCollectionInListbox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Elements}">
<ItemsControl ItemsSource="{Binding Collection}">
<!--<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Number}"/>
<Label Content="{Binding Path=SomeText}"/>
</StackPanel>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding ElementColl}"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
private Resource _resource = null;
public MainWindow()
{
InitializeComponent();
_resource = new Resource();
this.DataContext = _resource;
Element e1 = new Element();
e1.Number = 123;
e1.SomeText = "Apple";
_resource.ElementColl.Collection.Add(e1);
Element e2 = new Element();
e2.Number = 345;
e2.SomeText = "Bannana";
_resource.ElementColl.Collection.Add(e2);
Element e3 = new Element();
e3.Number = 567;
e3.SomeText = "Clementine";
_resource.ElementColl.Collection.Add(e3);
}
}
public class Element : INotifyPropertyChanged
{
private Int32 _number = 0;
public Int32 Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged("Number");
}
}
private string _someText = "";
public string SomeText
{
get { return _someText; }
set
{
_someText = value;
OnPropertyChanged("SomeText");
}
}
#region PropertyChanged
#endregion PropertyChanged
}
public class Elements : INotifyPropertyChanged
{
public Elements()
{
Collection = new ObservableCollection<Element>();
}
private ObservableCollection<Element> _col;
public ObservableCollection<Element> Collection
{
get { return _col; }
set
{
_col = value;
OnPropertyChanged("Collection");
}
}
#region PropertyChanged
#endregion PropertyChanged
}
public class Resource : INotifyPropertyChanged
{
public Resource()
{
ElementColl = new Elements();
}
private Elements _elements = null;
public Elements ElementColl
{
get { return _elements; }
set
{
_elements = value;
OnPropertyChanged("ElementColl");
}
}
#region PropertyChanged
#endregion PropertyChanged
}
It's because of your Canvas. Comment out those lines and you'll see your items.
<DataTemplate>
<!--<Canvas>-->
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Number}"/>
<Label Content="{Binding Path=SomeText}"/>
</StackPanel>
<!--</Canvas>-->
</DataTemplate>
Output:

How to prevent DataTemplate textbox losing focus when item is added to Itemscontrol?

I have an ItemsControl with ItemsSource bound to an IEnumerable<MyDataItem>.
The ItemTemplate consists of two textboxes. (Friendly name & name). This is how it looks like: http://i.stack.imgur.com/Rg1dC.png
As soon as the "Friendly name" field is filled in, I add an empty row. I use the LostKeyboardFocus event to check whether to add an empty "MyDataItem" and refresh the IEnumerable<MyDataItem> property.
The problem: I loose the focus when adding an item. So if I tab from friendly name to name and a new row is added, the focus is lost from name. How can I solve this?
EDIT: Underneath some code to show my problem. I want to be able to TAB from cell to cell. When both cells of a row are left empty, I want to remove that line. At the end I want to have an empty row (both cells empty). At this point the code works, but loses focus if you use tab. And working with a listbox makes that TAB doesn't work to go to the next item in list.
XAML:
<Window x:Class="Focus.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Focus"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="5"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox LostKeyboardFocus="TextBox_LostKeyboardFocus">
<TextBox.Text>
<Binding Path="FriendlyName" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<TextBox Grid.Column="2" LostKeyboardFocus="TextBox_LostKeyboardFocus">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Margin="10" ItemsSource="{Binding OrderedItems}" ItemTemplate="{DynamicResource DataTemplate1}" HorizontalContentAlignment="Stretch">
</ListBox>
</Grid>
Code behind:
using System.Windows;
using System.Windows.Input;
namespace Focus
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
private void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
MainViewModel vm = this.DataContext as MainViewModel;
vm.CheckToAddEmptyItem();
}
}
}
The ViewModel
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Focus
{
public class MainViewModel : INotifyPropertyChanged
{
private List<MyItem> _myItems = new List<MyItem>();
public IEnumerable<MyItem> OrderedItems
{
get { return _myItems.OrderBy(i => i.IsEmpty); }
}
internal void CheckToAddEmptyItem()
{
int count = _myItems.Count(i => i.IsEmpty);
if (count == 0)
{
_myItems.Add(new MyItem());
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("OrderedItems"));
}
else if (count > 1)
{
var items = _myItems.Where(i => i.IsEmpty).Skip(1).ToArray();
foreach (MyItem item in items)
_myItems.Remove(item);
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("OrderedItems"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
for(int i=1; i <= 5; ++i)
{
_myItems.Add(new MyItem() { FriendlyName = "Item #" + i, Name = "ITEM" + i });
}
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("OrderedItems"));
CheckToAddEmptyItem();
}
}
}
The MyItem class:
using System.ComponentModel;
namespace Focus
{
public class MyItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
PropertyChanged(this, new PropertyChangedEventArgs("IsEmpty"));
}
}
}
}
private string _friendlyName = string.Empty;
public string FriendlyName
{
get { return _friendlyName; }
set
{
if (value != _friendlyName)
{
_friendlyName = value;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("FriendlyName"));
PropertyChanged(this, new PropertyChangedEventArgs("IsEmpty"));
}
}
}
}
public bool IsEmpty
{
get { return string.IsNullOrEmpty(Name) && string.IsNullOrEmpty(FriendlyName); }
}
}
}
ItemsControl is not that friendly according to your needs as there are no properties to get a perticular selected item. Try using a Listbox Instead, as it exposes the properties like SelectedItem, SelectedIndex etc, Using these properties you can get the child control at any index value.
PS: I could elaborate my answer if you are looking to use alistbox and get the child elements.

Correlation heat map for windows presentation foundation

Does anyone knows any good tool for drawing correlation heat maps for WPF?
Example based on comments:
Original image source
The free WPF Toolkit has a TreeMap. You can define it in XAML as follows:
<vis:TreeMap ItemsSource="{Binding Path=HeatMap.Sectors}"
Interpolators="{StaticResource colourInterpolator}">
<vis:TreeMap.ItemDefinition>
<vis:TreeMapItemDefinition ValueBinding="{Binding MarketCap}">
<DataTemplate>
<Grid>
<Border x:Name="Border"
BorderBrush="Black"
BorderThickness="1"
Margin="1"
Opacity="0.5">
</Border>
<TextBlock Text="{Binding Name}"
TextWrapping="Wrap"
FontSize="20"
Margin="5"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</vis:TreeMapItemDefinition>
</vis:TreeMap.ItemDefinition>
</vis:TreeMap>
The above XAML is a snippet from an application I have written that shows financial HeatMaps. You can see a Silverlight version running here:
http://www.scottlogic.co.uk/blog/colin/xaml-finance/
(Just hit the 'heatmap' button)
If you are looking for a commercial product, I would suggest you look at the Telerik controls. Telerik has excellent controls for WPF. Included in the long list is a Heat Map control. Here is a link to the site where they list the heat map feature:
http://www.telerik.com/products/wpf/map.aspx
If you are looking to build something, here are a couple blog articles that lay out how to do it (with source provided):
http://www.garrettgirod.com/?p=111
http://www.nickdarnell.com/?p=833
The Syncfusion charting component appears to provide heatmaps.
Not a free component, but if you can get your hands on the Telerik library you could use the following:
http://www.telerik.com/products/wpf/heatmap.aspx
I have had to use it in the past for a few projects and it worked pretty well.
I used DevExpress with a custom ColorFormatter behaviour. I couldn't find anything on the market that did this out of the box. This took me a few days to develop. My code attaached below, hopefully this helps someone out there.
Edit: I used POCO view models and MVVM however you could change this to not use POCO if you desire.
Table2DViewModel.cs
namespace ViewModel
{
[POCOViewModel]
public class Table2DViewModel
{
public ITable2DView Table2DView { get; set; }
public DataTable ItemsTable { get; set; }
public Table2DViewModel()
{
}
public Table2DViewModel(MainViewModel mainViewModel, ITable2DView table2DView) : base(mainViewModel)
{
Table2DView = table2DView;
CreateTable();
}
private void CreateTable()
{
var dt = new DataTable();
var xAxisStrings = new string[]{"X1","X2","X3"};
var yAxisStrings = new string[]{"Y1","Y2","Y3"};
//TODO determine your min, max number for your colours
var minValue = 0;
var maxValue = 100;
Table2DView.SetColorFormatter(minValue,maxValue, null);
//Add the columns
dt.Columns.Add(" ", typeof(string));
foreach (var x in xAxisStrings) dt.Columns.Add(x, typeof(double));
//Add all the values
double z = 0;
for (var y = 0; y < yAxisStrings.Length; y++)
{
var dr = dt.NewRow();
dr[" "] = yAxisStrings[y];
for (var x = 0; x < xAxisStrings.Length; x++)
{
//TODO put your actual values here!
dr[xAxisStrings[x]] = z++; //Add a random values
}
dt.Rows.Add(dr);
}
ItemsTable = dt;
}
public static Table2DViewModel Create(MainViewModel mainViewModel, ITable2DView table2DView)
{
var factory = ViewModelSource.Factory((MainViewModel mainVm, ITable2DView view) => new Table2DViewModel(mainVm, view));
return factory(mainViewModel, table2DView);
}
}
}
ITable2DView.cs
namespace Interfaces
{
public interface ITable2DView
{
void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat);
}
}
Table2DView.xaml.cs
namespace View
{
public partial class Table2DView : ITable2DView
{
public Table2DView()
{
InitializeComponent();
}
static ColorScaleFormat defaultColorScaleFormat = new ColorScaleFormat
{
ColorMin = (Color)ColorConverter.ConvertFromString("#FFF8696B"),
ColorMiddle = (Color)ColorConverter.ConvertFromString("#FFFFEB84"),
ColorMax = (Color)ColorConverter.ConvertFromString("#FF63BE7B")
};
public void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat = null)
{
if (colorScaleFormat == null) colorScaleFormat = defaultColorScaleFormat;
ConditionBehavior.MinValue = minValue;
ConditionBehavior.MaxValue = maxValue;
ConditionBehavior.ColorScaleFormat = colorScaleFormat;
}
}
}
DynamicConditionBehavior.cs
namespace Behaviors
{
public class DynamicConditionBehavior : Behavior<GridControl>
{
GridControl Grid => AssociatedObject;
protected override void OnAttached()
{
base.OnAttached();
Grid.ItemsSourceChanged += OnItemsSourceChanged;
}
protected override void OnDetaching()
{
Grid.ItemsSourceChanged -= OnItemsSourceChanged;
base.OnDetaching();
}
public ColorScaleFormat ColorScaleFormat { get; set;}
public float MinValue { get; set; }
public float MaxValue { get; set; }
private void OnItemsSourceChanged(object sender, EventArgs e)
{
var view = Grid.View as TableView;
if (view == null) return;
view.FormatConditions.Clear();
foreach (var col in Grid.Columns)
{
view.FormatConditions.Add(new ColorScaleFormatCondition
{
MinValue = MinValue,
MaxValue = MaxValue,
FieldName = col.FieldName,
Format = ColorScaleFormat,
});
}
}
}
}
Table2DView.xaml
<UserControl x:Class="View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
xmlns:ViewModels="clr-namespace:ViewModel"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:behaviors="clr-namespace:Behaviors"
xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
DataContext="{dxmvvm:ViewModelSource Type={x:Type ViewModels:ViewModel}}"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type dxg:GridColumn}">
<Setter Property="Width" Value="50"/>
<Setter Property="HorizontalHeaderContentAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type dxg:HeaderItemsControl}">
<Setter Property="FontWeight" Value="DemiBold"/>
</Style>
</UserControl.Resources>
<!--<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="" Command="{Binding OnLoadedCommand}"/>
</dxmvvm:Interaction.Behaviors>-->
<dxg:GridControl ItemsSource="{Binding ItemsTable}"
AutoGenerateColumns="AddNew"
EnableSmartColumnsGeneration="True">
<dxmvvm:Interaction.Behaviors >
<behaviors:DynamicConditionBehavior x:Name="ConditionBehavior" />
</dxmvvm:Interaction.Behaviors>
<dxg:GridControl.View>
<dxg:TableView ShowGroupPanel="False"
AllowPerPixelScrolling="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</UserControl>

MVVM Binding a combobox

I have a very ordinary ViewModel and I am tring to bind a collection of values to a combobox. The problem is nothing is binding. I have checked the ViewModel constructor and the data is being loaded so I suspect its in my XAML but I just cant find out where.
public class OwnerOccupierAccountViewModel : ViewModelBase
{
readonly UserAccountContext _userAccountContext;
readonly LoadOperation<Structure> _loadStructures;
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
_userAccountContext = new UserAccountContext();
if (!DesignerProperties.IsInDesignTool)
{
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
_structures = new ObservableCollection<Structure>();
foreach (var structure in _loadStructures.Entities)
{
Structures.Add(structure);
}
}
}
<UserControl.Resources>
<viewmodel:OwnerOccupierAccountViewModel x:Key='ViewModel'></viewmodel:OwnerOccupierAccountViewModel>
</UserControl.Resources>
<ComboBox x:Name='cboApartments'
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
Width='200' />
Try intializing your ObservableCollection of Structures like that:
void _loadStructures_Completed(object sender, EventArgs e)
{
Structures = new ObservableCollection<Structure>(_loadStructures.Entities);
}
and as it was mentioned earlier i think you should change order here:
if (!DesignerProperties.IsInDesignTool)
{
//other code before
//_loadStructures = ...
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
//and now start loading
}
I did similar, very simple app to check what could gone wrong, but everything works well. I will show you my code, so you can compare and maybe you will find some bugs in your solution.
Structure.cs
public class Structure
{
public Structure(string name)
{
Name = name;
}
public string Name { get; set; }
}
StructureService.cs
public class StructureService
{
public void GetAllStructures(Action<IList<Structure>> CompleteCallback)
{
var temp = new List<Structure>()
{
new Structure("Str1"),
new Structure("Str2"),
new Structure("Str3"),
new Structure("Str4"),
new Structure("Str5"),
new Structure("Str6"),
new Structure("Str7")
};
CompleteCallback(temp);
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string prop)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
OwnerOccupierAccountViewModel:
public class OwnerOccupierAccountViewModel : ViewModelBase
{
StructureService service;
public OwnerOccupierAccountViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
service = new StructureService();
service.GetAllStructures((result) =>
{
Structures = new ObservableCollection<Structure>(result);
});
}
}
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Stuctures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
}
MainPage.xaml:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:OwnerOccupierAccountViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Source={StaticResource ViewModel},Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
If i were in your shoes i wll change xaml to such view:
SuggestedView:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<vm:OwnerOccupierAccountViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
but i understand that it is somehow impossible in your scenario?
Try replacing this line:
_structures = new ObservableCollection<Structure>();
with this:
Structures = new ObservableCollection<Structure>();
And set the binding of ComboBox to OneWay.
Edited to update solution:
Set DisplayMemberPath property of ComboBox as well:
DisplayMemberPath="StructureName"
The binding will only fire when the property is changed. The line setting the backing variable won't call the RaisePropertyChanged event. Even if it did it would be empty at this point anyway and you'd end up with an empty list.
_structures = new ObservableCollection<Structure>();
When you then add to the collection you aren't changing the property value, you're calling the getter so again the RaisePropertyChanged won't fire.
Structures.Add(structure);
You need to build a local collection then use that as the value for the Structures property. This should cause the binding to be triggered.
var structures = new ObservableCollection<Structure>();
foreach ...
Structures = structures;
You are binding directly to the ViewModel key as a source, but is it set as a DataContext anywhere?

Binding recursively in a WPF TreeView

I am trying to bind recursively to the children of an item in a TreeView. From what I can see on MSDN HierarchicalDataTemplate is the way to go, but thus far I've only been partially successful.
My class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DocumentText test = new DocumentText();
this.DataContext = test;
for (int i = 1; i < 5; i++)
{
test.AddChild();
}
foreach (DocumentText t in test.Children)
{
t.AddChild();
t.AddChild();
}
}
}
partial class DocumentText
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public override string ToString()
{
return Name;
}
public List<DocumentText> _children;
public List<DocumentText> Children
{
get { return this._children; }
}
public DocumentText()
{
_name = "Test";
_children = new List<DocumentText>();
}
public void AddChild()
{
_children.Add(new DocumentText());
}
}
My XAML:
In mainview.xaml:
<Window x:Class="treetest.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>
<TreeView Name="binderPanel" DockPanel.Dock="Left"
MinWidth="150" MaxWidth="250" Background="LightGray"
ItemsSource="{Binding Children}">
</TreeView>
</Grid>
</Window>
In app.xaml:
<HierarchicalDataTemplate x:Key="BinderTemplate"
DataType="{x:Type src:DocumentText}" ItemsSource="{Binding Path=/Children}">
<TreeViewItem Header="{Binding}"/>
</HierarchicalDataTemplate>
This code produces a list of the first children, but the nested children are not displayed.
The primary problem in what you posted is that you haven't connected the HierarchicalDataTemplate as the TreeView's ItemTemplate. You need to either set ItemTemplate="{StaticResource BinderTemplate}" or remove the x:Key to apply the template to all DocumentText instances. You should also change the TreeViewItem in the template to a TextBlock - the TreeViewItem is generated for you and what you put in that template is applied to it as a HeaderTemplate.

Resources