I have a problem with data binding (binding update?). After launching the application, I load the images and want to display their histograms on a scale of 0-255. The variable 'frequency' already has the data ready. And I want to bind them to xaml to show some kind of chart. XAML code works well with some static data, but assigning value to 'frequency' it doesn't show anything.
XAML:
<ItemsControl Name="ItemsControlName" ItemsSource="{Binding ChartValues}">
<ItemsControl.DataContext>
<local:DicomChart/>
</ItemsControl.DataContext>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Height="{Binding }" Width="4" VerticalAlignment="Bottom"
Fill="#eee" Stroke="Gray" Margin="-1.0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#:
private void RegionGrowing(object sender, RoutedEventArgs e)
{
int[] frequency = new int[256];
for (int m = 0; m < 512; m++)
{
for (int n = 0; n < 512; n++)
{
frequency[database[pictureIndex, m, n]] += 1;
}
}
DicomChart dc = new DicomChart();
dc.ChartValues = frequency.ToArray();
}
DicomClass class:
public class DicomChart
{
public DicomChart()
{
}
public int[] ChartValues { get; set; }
}
Try use additional class like DicomChartItem and fill observable collection with it, something like that
C#:
public class DicomChart
{
public ObservableCollection<DicomChartItem> ChartItems{ get; set; } = new ObservableCollection<DicomChartItem>();
}
public class DicomChartItem
{
public int _Value { get; set; }
public DicomChartItem(int Value)
{
_Value = Value;
}
}
XAML:
<ItemsControl ItemsSource="{Binding ChartItems}">
<ItemsControl.DataContext>
<local:DicomChart/>
</ItemsControl.DataContext>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DicomChartItem}">
<Rectangle Height="{Binding _Value}" Width="4" VerticalAlignment="Bottom"
Fill="#eee" Stroke="Gray" Margin="-1.0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Observable collection must be filled in the loop for calling CollectionChanged event, in another case you need to use PropertyChanged event from INotifyPropertyChanged interface.
C#:
private void RegionGrowing(object sender, RoutedEventArgs e)
{
int[] frequency = new int[256];
for (int m = 0; m < 512; m++)
{
for (int n = 0; n < 512; n++)
{
frequency[database[pictureIndex, m, n]] += 1;
}
}
DicomChart dc = new DicomChart();
foreach (var item in frequency)
dc.ChartItems.Add(new DicomChartItem(item));
}
You have to replace int[] with ObservableCollection<int> like I said. It's not going to work with a simple array because UI isn't notified about changes.
Add x:Name="Items" to your ItemsControl in order to be able to access it from the code behind;
Add this code in your RegionGrowing: var ds = (DicomChart) Items.DataContext;
Add values to your ds object like ds.ChartValues.Add(<your_value>).
Related
I create multiple checkbox using ItemsControl in my WPF. But I need to make a limit by 20 for checkbox that can be checked/ticked by user. How do I can check the checked checkbox?
I tried to research this as much as I can, and even binding checkbox to multiple command, but none of it is working. Below is my code to get through the checkbox that were inside the Itemscontrol. after, IsChecked.
for (int i = 0; i < ItemsControlUnitPerStrip.Items.Count; i++)
{
ContentPresenter container = (ContentPresenter)ItemsControlUnitPerStrip.ItemContainerGenerator.ContainerFromItem(ItemsControlUnitPerStrip.Items[i]);
CheckBox checkBoxChecked = container.ContentTemplate.FindName("CheckBoxUnitPerStrip", container) as CheckBox;
if (checkBoxChecked.IsChecked == true)
{
//iOPC.WriteTag(checkBoxChecked.Uid, checkBoxChecked.IsChecked);
}
}
My XAML code
<GroupBox x:Name="GroupBoxSamplingModeStrip" Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ItemsControlUnitPerStrip"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
Here the function code on how I generate the checkbox
private void initializeUnitPerStrip()
{
unitPerStrip = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
unitPerStrip.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
});
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
1) Binding checkbox property with notify property changed events:
public class UtilitiesModel : NotifyBase
{
private bool _IsChecked = false;
...
// Key
// Tag
...
public bool IsChecked
{
get {return _IsChecked;}
set
{
_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
For convenience, the part responsible for events is placed in a separate small class:
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
XAML changes:
..
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
..
2) Next we shall track events of changing state of checkboxes and add a counter for checked checkboxes;
A slight change in function:
private void initializeUnitPerStrip()
{
..
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
UtilitiesModel item = new UtilitiesModel
{
Key = "[{c}, {r}]",
Tag = "{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
};
item.PropertyChanged += PropertyChangedFunc;
unitPerStrip.Add(item);
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
Add func for checking property changed events:
private void PropertyChangedFunc(object sender, PropertyChangedEventArgs e)
{
UtilitiesModel obj = sender as UtilitiesModel;
if(obj==null)return;
if (e.PropertyName == "IsChecked")
{
iCount1 = obj.IsChecked ? iCount1 + 1 : iCount1 - 1;
if (iCount1 > 19) //Block checking
{
obj.IsChecked = false;
}
}
}
Where iCount1 - is a counter checked checkboxes, just declare it anywhere, for example in samplingModeModel
This answer uses MVVM, so the names of your controls in XAML have been removed as they are not needed in MVVM. Your XAML would look like this:
<Button Content="Count CheckBoxes" Command="{Binding CommandCount}"
HorizontalAlignment="Left"/>
<GroupBox Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ItemsSource="{Binding Path=UnitPerStrip}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip >
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
The only real difference in the XAML is adding the binding to the 'IsChecked' property in the CheckBox, and setting up a binding to a property called 'UnitPerStrip' for the ItemsSource of the ItemsControl.
In the ViewModel then, you need to set up the UnitPerStrip property:
private List<UtilitiesModel> unitPerStrip;
public List<UtilitiesModel> UnitPerStrip
{
get
{
return unitPerStrip;
}
set
{
if (value != unitPerStrip)
{
unitPerStrip = value;
NotifyPropertyChanged("UnitPerStrip");
}
}
}
The UtilitiesModel class needs a new property called IsChecked to keep track of when the CheckBox is checked. That way you don't have to muck about with messy UI code. It can all be done neatly in the back-end data.
public class UtilitiesModel
{
public string Key { get; set; }
public string Tag { get; set; }
public bool IsChecked { get; set; }
}
The code for generating the CheckBoxes doesn't change a lot. You just need to make sure to add in the IsChecked property and then assign the results to the UnitPerStrip property when finished.
private void initializeUnitPerStrip()
{
List<UtilitiesModel> ups = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
ups.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}",
IsChecked = false;
});
}
}
UnitPerStrip = ups;
}
Then the code to check how many CheckBoxes are checked is very straightforward. It only examines the data in the ViewModel and never has to worry about messing with any of the messiness of the UI:
private void Count()
{
int count = 0;
foreach (UtilitiesModel item in UnitPerStrip)
{
if (item.IsChecked) count++;
}
MessageBox.Show(count.ToString());
}
In case you don't like to add a special IsChecked property to your model you could use a IValueConverter.
A property like IsChecked is view related and shouldn't be part of the view model (if you can avoid it). When you change the control that binds to the view model you might also need to change this property or rename it (e.g. IsExpanded etc.).
I recommend to avoid properties that reflect a visual state inside a view model in general. Your view model would become bloated if adding properties like IsVisible, IsPressed, IsToggled. This properties rather belong to a Control.
The converter (or DataTrigger) approach leaves your binding data models unchanged (with data related properties only). To keep the view model clean and free from UI logic like adjusting properties like IsVisible or IsChecked to reflect e.g. reordering of the collection to the view (e.g. insert or sort operations), all UI logic and visual details like enabling or disabling the controls
should be handled by converters and triggers only:
<!-- Decalare the converter and set the MaxCount property -->
<Window.Resources>
<local:ItemCountToBoolenaConverter x:Key="ItemCountToBoolenaConverter"
MaxCount="20" />
</Window.Resources>
<! -- The actual DataTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Converter={StaticResource ItemCountToBoolenaConverter}}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
The ItemCountToBoolenaConverter:
[ValueConversion(typeof(ListBoxItem), typeof(bool))]
class ItemCountToBoolenaConverter : IValueConverter
{
public int MaxCount { get; set; }
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ListBoxItem itemContainer && TryFindParentElement(itemContainer, out ItemsControl parentItemsControl))
{
return parentItemsControl.Items.IndexOf(itemContainer.Content) < this.MaxCount;
}
return Binding.DoNothing;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
// Consider to make this an Extension Method for DependencyObject
private bool TryFindVisualParent<TParent>(DependencyObject child, out TParent resultElement) where TParent : DependencyObject
{
resultElement = null;
if (child == null)
return false;
DependencyObject parentElement = VisualTreeHelper.GetParent(child);
if (parentElement is TParent)
{
resultElement = parentElement as TParent;
return true;
}
return TryFindVisualParent(parentElement, out resultElement);
}
}
problem
I have a 2d array of some objects. For greater flexibility, it is an array of interface types. At the start, some cells can be null, but it fills with data later. I bind this array to UniformGrid in WPF. When I create a new object and add it in the array my grid doesn't change. The question is how to design bindind that if bound objects change (not the properties in object), the grid will change too.
Similar question was there
but without decisions
my code
cells in array
(simple classes with method that can change properties to be sure binding works correctly. nothing special here)
interface IOrg
{
string Text { get; }
SolidColorBrush SolidColor { get; }
void SetColor();
void SetText();
}
class IOrgA : IOrg
{
Random random;
public IOrgA(Random random)
{
SolidColor = new SolidColorBrush();
this.random = random;
SetColor();
SetText();
}
public string Text { get; private set; }
public SolidColorBrush SolidColor { get; private set; }
public void SetColor()
{
SolidColor.Color = Color.FromRgb(255, 255, (byte)random.Next(256));
}
public void SetText()
{
Text = random.Next(1000).ToString();
}
}
class IOrgB : IOrg
{
Random random;
public IOrgB(Random random)
{
SolidColor = new SolidColorBrush();
this.random = random;
SetColor();
SetText();
}
public string Text { get; private set; }
public SolidColorBrush SolidColor { get; private set; }
public void SetColor()
{
SolidColor.Color = Color.FromRgb((byte)random.Next(256), 255, 255);
}
public void SetText()
{
Text = random.Next(1000, 2000).ToString();
}
}
main class
class Table
{
//array with data
private IOrg[,] _layer;
private Random _random = new Random();
public Table(int heigth, int width)
{
Height = heigth;
Widht = width;
_layer = new IOrg[heigth, width];
ListLayer = new List<List<IOrg>>();
FillLayer();
FillList();
}
public int Height { get; private set; }
public int Widht { get; private set; }
//list for binding
public List<List<IOrg>> ListLayer { get; private set; }
void FillLayer()
{
for (int i = 0; i < Height; i++)
{
for (int j = 0; j < Widht; j++)
{
//randomly fill array
if (_random.Next(30) == 1)
{
IOrg org;
if (_random.Next(2) == 0)
{
org = new IOrgA(_random);
}
else
{
org = new IOrgB(_random);
}
_layer[i, j] = org;
}
}
}
}
}
xaml
<Window.Resources>
<!-- size of uniform gris -->
<sys:Int32 x:Key="GridSize" >50</sys:Int32>
<!-- template for each row -->
<DataTemplate x:Key="RowDataTemplate">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource ElementDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="RowGrid" Columns="{StaticResource GridSize}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
<!-- template for each element in row -->
<DataTemplate x:Key="ElementDataTemplate">
<TextBlock Text="{Binding Text, FallbackValue=__}">
<TextBlock.Background>
<SolidColorBrush Color="{Binding SolidColor.Color, FallbackValue=#123456}"></SolidColorBrush>
</TextBlock.Background>
</TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="14*" />
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" x:Name="lst" ItemTemplate="{DynamicResource RowDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="World" Rows="{StaticResource GridSize}" Background="Bisque"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button Grid.Row="1" Width="100" Height="50" Click="Button_Click">
button
</Button>
</Grid>
binding
table = new Table(50, 50);
lst.ItemsSource = table.ListLayer;
I randomly fill my array with data, bind it in UniformGrid, and provide FallbackValue for null value objects. When I add a new object to the array, nothing changes. How do Ifix this problem?
Instead of using List as below, try with an ObservableCollection instead.
public List<List<IOrg>> ListLayer { get; private set; }
At any point, if you need to add an object to the collection, just raise the PropertyChanged event for the INotifyPropertyChanged interface which your ViewModel(DataContext) class needs to implement.
I have text entries which have both a type and a category. To store them I use a Dictionary<string, Dictionary<string, ModelEntry>> ModelEntries. Not the prettiest thing I know but works perfectly as a 2-dimensional array with string indexes. In this way for instance I can call ModelEntries["type1"]["category2"].Content to get the text for the corresponding entry. The number of categories and types is fixed to 3 and 4.
How can I display them in a table-like Grid by using a DataTemplate?
My goal is something like this:
where the boxes of the grid would be filled with the content of the corresponding entry.
I've gotten so far to this:
<StackPanel Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="4" Grid.RowSpan="3">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RichTextBox></RichTextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
And I don't know how to continue. Any help would be appreciated.
Side note: I know that since the rows and columns are fixed number I can do what I want with just more code and statically define 12 RichTextBoxes. The reason I want a DataTemplate approach is that there will be interaction with the other fields when a user clicks on a text. Browsing a Dictionary will be times easier than manually checking all statically defined RichTextBoxes.
One of the more common mistakes made when doing WPF development is that people try to fit their view to their existing data model when it really should be the job of an intermediate layer (i.e. a view model) to mediate between the two. XAML is extremely powerful and can do some clever things but if you find yourself having to do too much work in the view or over-relying on things like converters to bend your data into a more useful form then it's usually a good indication that your view model isn't doing its job properly.
Take a step back and look at it from the other direction: you have a view in mind, what type of data do you need to allow that view to work? First of all you're displaying two things: your headers (at the top and left) and your cells, so start by creating classes in your view model for those:
public class CustomGridHeader
{
public int Column { get; set; }
public int Row { get; set; }
public string Header { get; set; }
}
public class CustomGridCell
{
public int Column { get; set; }
public int Row { get; set; }
public object Content { get; set; }
}
Now your view model is going to need a collection of things to display (both headers and cells) and a function to reference the data in your dictionary. I'll leave the translation step to you but here's enough to get started:
private int _NumColumns;
public int NumColumns
{
get { return this._NumColumns; }
set { this._NumColumns = value; RaisePropertyChanged(() => this.NumColumns); }
}
private int _NumRows;
public int NumRows
{
get { return this._NumRows; }
set { this._NumRows = value; RaisePropertyChanged(() => this.NumRows); }
}
private object[] _GridData;
public object[] GridData
{
get { return this._GridData; }
set { this._GridData = value; RaisePropertyChanged(() => this.GridData); }
}
private string[] Types = new string[] { "Type1", "Type2", "Type3" };
private string[] Categories = new string[] { "Cat1", "Cat2", "Cat3" };
private void UpdateGridData()
{
this.NumColumns = this.Categories.Length + 1;
this.NumRows = this.Types.Length + 1;
this.GridData = new object[(this.Categories.Length + 1) * (this.Types.Length + 1)];
// set category and type headers
for (int i=0; i<this.Categories.Length; i++)
this.GridData[i + 1] = new CustomGridHeader { Column = i + 1, Row = 0, Header = this.Categories[i] };
for (int i=0; i<this.Types.Length; i++)
this.GridData[(i + 1) * this.NumColumns] = new CustomGridHeader { Column = 0, Row = i + 1, Header = this.Types[i] };
// fill the cells
for (int i = 0; i < this.Categories.Length; i++)
for (int j = 0; j < this.Types.Length; j++)
this.GridData[(i + 1) * this.NumColumns + j + 1] = new CustomGridCell {Column = i+1, Row = j+1, Content = String.Format("{0}/{1}", i, j)};
}
Code-wise that's all you need, you now have your data in a form ready to be consumed by your view. You want to display your data in a grid, but functionally what you're really doing is displaying a list of items, so what you really should be doing is using an ItemsControl but specifying a Grid as the Panel Template. You'll also need to set the ItemContainerStyle to set the grid column and row for each element. Finally you'll need two data templates: one that specifies how your headers should be displayed and another to specify how the cells should be displayed:
<ItemsControl ItemsSource="{Binding GridData}" Margin="50">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:CustomGridHeader}">
<TextBlock Text="{Binding Header}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:CustomGridCell}">
<Border BorderBrush="Black" BorderThickness="1" Margin="0,0,-1,-1">
<TextBlock Text="{Binding Content}" Background="AliceBlue" HorizontalAlignment="Center" VerticalAlignment="Center" Width="50" Height="50"/>
</Border>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Behaviors>
<local:GridSizeBehavior Columns="4" Rows="4"/>
</i:Interaction.Behaviors>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding Column}" />
<Setter Property="Grid.Row" Value="{Binding Row}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The only missing piece of the puzzle is GridSizeBehavior. The Grid control doesn't have "NumColumns" or "NumRows" fields, but you can effectively add it yourself with an attached behaviour (for this and other reasons I'm not convinced that Grid is the best control for this task but that's what you've asked for so that's what I'm providing):
public class GridSizeBehavior : Behavior<Grid>
{
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
// Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(GridSizeBehavior), new PropertyMetadata(0, OnColumnsChanged));
static void OnColumnsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var behaviour = depObj as GridSizeBehavior;
if (behaviour == null)
return;
behaviour.SetColumns();
}
private void SetColumns()
{
var grid = this.AssociatedObject;
if (grid == null)
return;
int columns = this.Columns;
grid.ColumnDefinitions.Clear();
for (int i = 0; i < columns; i++)
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
}
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
// Using a DependencyProperty as the backing store for Rows. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int), typeof(GridSizeBehavior), new PropertyMetadata(0, OnRowsChanged));
static void OnRowsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var behaviour = depObj as GridSizeBehavior;
if (behaviour == null)
return;
behaviour.SetRows();
}
private void SetRows()
{
var grid = this.AssociatedObject;
if (grid == null)
return;
int Rows = this.Rows;
grid.RowDefinitions.Clear();
for (int i = 0; i < Rows; i++)
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
}
protected override void OnAttached()
{
base.OnAttached();
SetColumns();
SetRows();
}
}
Put all those together and you'll get the result you're after:
The main downside to the exact implementation shown here is that the view won't update automatically in response to changes to your original dictionaries, but given that Dictionary doesn't support collection change notification that was never going to happen anyway. Yes, you could have done all this entirely in your view using converters to enumerate your columns and rows and bind to your dictionary data directly, but at the end of the day you would have found that your code would have pretty much matched what I've done here in the view model anyway, so you may as well do it there in a form that's easy to debug and unit test.
I have a StackPanel in my grid and it displays a dynamically generated list of buttons, I'm trying to figure out how to get them displayed ascending alphabeticaly.
XAML Code
<StackPanel Grid.Column="0" Name="AreaStackPanel" Orientation="Vertical" Background="Khaki">
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Background="Beige">
<GroupBox Name="StatusGroupBox" Header="Work Items" Width="234">
<StackPanel Name="StatusStackPanel"></StackPanel>
</GroupBox>
</StackPanel>
C# Code
private void LoadExistingAreas()
{
List<string> collections = Reporter.GetCollections();
string unique = "";
foreach (string collection in collections)
{
string areaName = Path.GetFileName(collection);
if (unique.Contains(areaName)) continue;
unique += areaName;
Button areaButton = new Button();
areaButton.Click += areaButton_Click;
areaButton.Margin = new Thickness(2);
areaButton.Content = areaName;
AreaStackPanel.Children.Add(areaButton);
Area
}
}
I would recommend using MVVM to accomplish this task. I am posting an example of what would work in a fairly clean fashion.
Your XAML should look as follows:
<Window x:Class="ItemTemplateDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ItemTemplateDemo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding ButtonDescriptions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="2" Content="{Binding Name}" Command="{Binding OnClickCommand}"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The main view model. You should sort and filter your data in here as well
public class MainViewModel
{
public ObservableCollection<ButtonDescription> ButtonDescriptions { get; private set; }
public MainViewModel()
{
ButtonDescriptions = new ObservableCollection<ButtonDescription>();
for (int i = 0; i < 10; i++)
{
var bd = new ButtonDescription() { Name = "Button " + i };
ButtonDescriptions.Add(bd);
}
}
}
The button description holds the attributes for the button
public class ButtonDescription
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private ICommand onClickCommand;
public ICommand OnClickCommand
{
get { return onClickCommand; }
set { onClickCommand = value; }
}
public ButtonDescription()
{
}
}
I would also recommend reading the following if you are not familiar with MVVM MVVM intro
I have a ListBox with a UniformGrid layout and I'm trying to change the "Columns" property, but I don't know the best way of doing that. I tried binding to a property or to create a new layout programatically, but I can't figure it out.
<ListBox x:Name="ImagesList" ItemsSource="{Binding Path=GridImages}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True" Columns="3" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I'm trying to change between 1 and 3 columns, when the user click on two buttons. I've tried binding with Columns="{Binding Path=MyColumnCount}", but it never changes, and tried to set a x:Name and access from my code, without success. I also tried to instantiate a new UniformGrid, but I've read that I need a factory for that, so I can't set a different Columns value.
I thought maybe the ItemsPanelTemplate didn't inherit the ListBox' DataContext, but it does, so your Binding should work:
<ListBox x:Name="ImagesList" ItemsSource="{Binding Path=GridImages}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True" Columns="{Binding Path=MyColumnCount}"
VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Try it with this simple ViewModel which updates MyColumnCount on a timer:
public class ImagesVM : INotifyPropertyChanged
{
private System.Threading.Timer _timer;
private int _colIncrementor = 0;
public ImagesVM()
{
_timer = new System.Threading.Timer(OnTimerTick, null,
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1));
_gridImages = new string[] {
"http://www.anbg.gov.au/images/flags/semaphore/a-icon.gif",
"http://www.anbg.gov.au/images/flags/semaphore/b-icon.gif",
"http://www.anbg.gov.au/images/flags/semaphore/c-icon.gif",
"http://www.anbg.gov.au/images/flags/semaphore/d-icon.gif",
"http://www.anbg.gov.au/images/flags/semaphore/e-icon.gif",
"http://www.anbg.gov.au/images/flags/semaphore/f-icon.gif",
};
}
private void OnTimerTick(object state)
{
this.MyColumnCount = (_colIncrementor++ % 3) + 1;
}
private int _myColumnCount = 3;
public int MyColumnCount
{
get { return _myColumnCount; }
set
{
_myColumnCount = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("MyColumnCount"));
}
}
private string[] _gridImages = null;
public string[] GridImages
{
get { return _gridImages; }
}
public event PropertyChangedEventHandler PropertyChanged = (s, e) => { };
}