I'm trying to create the PropertyGrid dynamically in code. So far, I can create and customize a PropertyGrid inside the XAML credits to: XCeed PropertyGrid customizing IntegerUpDown
:
MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
Sample or = new Sample();
pg.SelectedObject = or;
pg.ShowAdvancedOptions = true;
EditorDefinition ed = new EditorDefinition();
PropertyDefinition pd = new PropertyDefinition();
pd.Name = "Value";
ed.PropertyDefinitions.Add(pd);
DataTemplate dt = new DataTemplate();
FrameworkElementFactory fac = new FrameworkElementFactory(typeof(PropertyGridEditorIntegerUpDown));
dt.VisualTree = fac;
DependencyProperty dp = PropertyGridEditorIntegerUpDown.DefaultValueProperty;
fac.SetValue(dp, 10);
ed.EditorTemplate = dt;
pg.EditorDefinitions.Add(ed);
}
MainWindow.xaml:
<Window
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:WpfApp1"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid" x:Class="WpfApp1.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<xctk:PropertyGrid x:Name="pg">
<xctk:PropertyGrid.EditorDefinitions >
<xctk:EditorDefinition >
<xctk:EditorDefinition.PropertiesDefinitions >
< xctk:PropertyDefinition Name = "Value" />
</xctk:EditorDefinition.PropertiesDefinitions >
<xctk:EditorDefinition.EditorTemplate >
<DataTemplate >
<xctk:PropertyGridEditorIntegerUpDown Increment = "10" Value = "{Binding Value}" Maximum = "40" MinHeight = "0" Minimum = "-30" />
</DataTemplate >
</xctk:EditorDefinition.EditorTemplate >
</xctk:EditorDefinition >
</xctk:PropertyGrid.EditorDefinitions >
</xctk:PropertyGrid >
</Window>
and the Sample class:
public class Sample
{
private int _Value;
#region Public Properties
[Category("Sample")]
[DisplayName("Sample Value")]
[DefaultValue(3)]
public int Value { set; get; }
#endregion
}
This would be the equivalent code:
EditorDefinition ed = new EditorDefinition();
PropertyDefinition pd = new PropertyDefinition();
pd.Name = "Value";
ed.PropertiesDefinitions.Add(pd);
FrameworkElementFactory fac = new FrameworkElementFactory(typeof(PropertyGridEditorIntegerUpDown));
fac.SetBinding(PropertyGridEditorIntegerUpDown.ValueProperty, new Binding("Value"));
fac.SetValue(PropertyGridEditorIntegerUpDown.IncrementProperty, 10);
DataTemplate dt = new DataTemplate { VisualTree = fac };
dt.Seal();
ed.EditorTemplate = dt;
pg.EditorDefinitions.Add(ed);
Related
I have a project where I have a charting area.
I have choosen LiveCharts ( http://lvcharts.net ) for that matter.
So far so good, the charting works with databinding when added in xaml:
<UserControl x:Class="Work_Task_Planner_Sheduler.Views.TeamTaskProgressChartView"
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:Work_Task_Planner_Sheduler.Views"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<lvc:CartesianChart x:Name="TeamTaskProgressChartComponent">
<lvc:CartesianChart.Series>
<lvc:LineSeries Title="Julian" Values="{Binding Taskprogress_julian}" />
</lvc:CartesianChart.Series>
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Time"></lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="TaskProgress"></lvc:Axis>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
</Grid>
</UserControl>
The issue is: The Line series is hardcoded in xaml. Who knows if the team gets bigger/smaller over time or the team members change. Therefore I want to create the line series at runtime.
This is what I have so far, unfortunately the series stays empty:
CartesianChart chart = this.TeamTaskProgressChartComponent;
foreach(string user in TeamMembers)
{
LineSeries lineseries = new LineSeries();
string title = user.Split('.')[0];
lineseries.Title = title;
Binding databinding = new Binding();
databinding.Source =
Datarefresh.mainWindow.mainViewModel.TeamTaskProgressChartViewModel.TaskProgressCounts;
DependencyProperty LineSeriesProperty = DependencyProperty.Register(title+"Property", typeof(int), typeof(LineSeries), new PropertyMetadata(0));
lineseries.SetBinding(LineSeriesProperty, databinding);
chart.series.Add(lineseries);
}
You have to set the source for the binding.
CartesianChart chart = new CartesianChart();
foreach(string user in TeamMembers)
{
LineSeries lineseries = new LineSeries();
string title = user.Split('.')[0];
lineseries.Title = title;
Binding databinding = new Binding("Taskprogress_"+title);
//===========Set source here before setting the binding to the lineseries===========
databinding.Source = "your datasource";
// the following line is pseudocode derived from Microsoft reference
// https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-create- a-binding-in-code?view=netframeworkdesktop-4.8
lineseries.SetBinding(LineSeries.Values, databinding);
}
Actually the Process is quite Simple:
Create a Series collation. IMPORTANT: The series collection must be initialized in the constructor so that it is ready when the chart loads:
public class TeamTicketResolveChartViewModel
{
public SeriesCollection ChartSeries { get; set; }
public string[] TicketResolveLabels { get; set; }
public TeamTicketResolveChartViewModel()
{
ChartSeries = new SeriesCollection();
foreach (string user in TeamMembers.ServiceDesk)
{
LineSeries lineseries = new LineSeries();
string title = user.Split('.')[0];
lineseries.Title = title;
ChartSeries.Add(lineseries);
}
}
// other functions
}
In xaml, bind the chart to the series Collection:
<lvc:CartesianChart x:Name="TeamTicketResolveChartComponent" Series="{Binding ChartSeries}" />
Now updating the Chart Data will work as intended:
public void LoadChartData2(List<List<(DateTime time, int resolved)>> input)
{
for (int i = 0; i < input.Count; i++)
{
ChartSeries[i].Values = new ChartValues<int>(input[i].Select(c => c.resolved));
}
DateTime[] dates = input[0].Select(c => c.time).ToArray();
List<string> labels = new List<string>();
foreach (DateTime time in dates) labels.Add(time.ToString("HH:mm:ss"));
TicketResolveLabels = labels.ToArray();
}
public void AddChartPoint(List<List<(DateTime time, int resolved)>> input)
{
// Chartseries[i].values.Add()
}
I use chart plugin for WPF:
https://lvcharts.net
In xaml I have:
<Window x:Class="GoogleDriveManager.WPF.ChartWindow"
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:GoogleDriveManager.WPF" xmlns:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="Chart statistics" Height="450" Width="800">
<Grid>
<wpf:PieChart LegendLocation="Bottom" Series="{Binding Items}"/>
</Grid>
</Window>
and code behind:
public ChartWindow()
{
InitializeComponent();
DataContext = this;
Items = new SeriesCollection();
ContentRendered += (s, ev) =>
{
Dictionary<string, List<string>> data = GetData(...);
foreach (var item in data)
{
ISeriesView series = new PieSeries(new { title = item.Key });
IChartValues values = new ChartValues<string>(item.Value);
series.Values = values;
Items.Add(series);
}
};
}
public SeriesCollection Items { get; }
But seems that the window is empty.
You should transfer string values to double
Use PieSeries properties Title to assign title
I create a simple case and this code works:
ContentRendered += (s, ev) =>
{
Dictionary<string, List<string>> data = new Dictionary<string, List<string>>(){
{"AAA", new List<string>(){"1","2"}},
{"BBB", new List<string>(){"3","4"}}
};
foreach (var item in data)
{
var curValues = item.Value.Select(x => double.Parse(x)).ToList();
ISeriesView series = new PieSeries{
Title = item.Key,
Values = new ChartValues<double>(curValues),
DataLabels = true
};
Items.Add(series);
}
};
I have content object in wcf.
I try store in content property grid but it not fill the entire length.
Function return grid:
private Grid ChangeContentObject()
{
Grid g = new Grid();
g.Background = new SolidColorBrush(Colors.Red);
g.HorizontalAlignment = HorizontalAlignment.Stretch;
g.VerticalAlignment = VerticalAlignment.Stretch;
ColumnDefinition columnDefinitionForPath = new ColumnDefinition();
columnDefinitionForPath.Width = new GridLength(4,GridUnitType.Star);
ColumnDefinition columnDefinitionForEmpty = new ColumnDefinition();
columnDefinitionForEmpty.Width = new GridLength(6, GridUnitType.Star);
g.ColumnDefinitions.Add(columnDefinitionForPath);
g.ColumnDefinitions.Add(columnDefinitionForEmpty);
WindowsShapes.Path p = new WindowsShapes.Path();
p.Stroke = new SolidColorBrush(Colors.Brown);
p.StrokeThickness = 2;
p.HorizontalAlignment = HorizontalAlignment.Stretch;
var b = new Binding
{
Source = "M50,0 L0,0 L0,50 L50,50"
};
BindingOperations.SetBinding(p, WindowsShapes.Path.DataProperty, b);
p.Stretch = Stretch.Fill;
g.Children.Add(p);
Grid.SetColumn(p, 0);
return g;
}
Code for set content:
objectVisual.Content = ChangeContentObject();
objectVisual property:
objectVisual.VerticalAlignment = Stretch
objectVisual.VerticalAlignment
objectVisual.Width = 100
objectVisual.Height = 50
I get next result:
Why grid does not fill the entire length?
Your Grid has 2 columns. The second column is empty, and therefore collapsed, so you can't see it. Binding something to it solves the "problem". Also, modified your Path data for this demo, because the one you had is no good. Take a look:
private Grid ChangeContentObject()
{
Grid g = new Grid();
g.Background = new SolidColorBrush(Colors.Red);
g.HorizontalAlignment = HorizontalAlignment.Stretch;
g.VerticalAlignment = VerticalAlignment.Stretch;
// add grid line to show columns border
g.ShowGridLines = true;
ColumnDefinition columnDefinitionForPath = new ColumnDefinition();
columnDefinitionForPath.Width = new GridLength(4, GridUnitType.Star);
ColumnDefinition columnDefinitionForEmpty = new ColumnDefinition();
columnDefinitionForEmpty.Width = new GridLength(6, GridUnitType.Star);
g.ColumnDefinitions.Add(columnDefinitionForPath);
g.ColumnDefinitions.Add(columnDefinitionForEmpty);
var p1 = new Path();
p1.Stroke = new SolidColorBrush(Colors.Brown);
p1.StrokeThickness = 2;
p1.Stretch = Stretch.Fill;
p1.HorizontalAlignment = HorizontalAlignment.Stretch;
var b1 = new Binding
{
// modified path data
Source = "M 10,100 C 10,300 300,-200 300,100"
};
BindingOperations.SetBinding(p1, Path.DataProperty, b1);
var p2 = new Path();
p2.Stroke = new SolidColorBrush(Colors.Brown);
p2.StrokeThickness = 2;
p2.Stretch = Stretch.Fill;
p2.HorizontalAlignment = HorizontalAlignment.Stretch;
var b2 = new Binding
{
// modified path data
Source = "M 100,10 C 100,30 -200,100 100,300"
};
BindingOperations.SetBinding(p2, Path.DataProperty, b2);
g.Children.Add(p1);
g.Children.Add(p2);
Grid.SetColumn(p1, 0);
Grid.SetColumn(p2, 1);
return g;
}
Update:
Normally, a Grid would stretch and expand on its own, no need at all to "fill it with empty space". The problem here is caused by the fact that you're placing your Grid enclosed within objectVisual, which apparently is a ContentControl (you didn't make it clear in your post). So, instead you should make objectVisual a type derived from a Panel, like, let's say, another Grid.
And then, replace this:
objectVisual.Content = ChangeContentObject();
with this:
objectVisual.Children.Add(ChangeContentObject());
And you'll get what you want:
Update 2:
Well, your pastebin code is slightly different from your original code, and although that would work too, you misunderstood what I said about the Grid. You don't need your var gridChild, you can keep the second column empty, like in your original question. Notice I commented it out. So, in order to clarify, I am posting full MCVE sample code below. The result looks just like the second image I posted above previously.
UserControl CS:
public partial class SilverlightControl3 : UserControl
{
public SilverlightControl3()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
objectVisual.Children.Add(ChangeContentObject());
}
private Grid ChangeContentObject()
{
Grid g = new Grid();
g.Background = new SolidColorBrush(Colors.Red);
g.HorizontalAlignment = HorizontalAlignment.Stretch;
g.VerticalAlignment = VerticalAlignment.Stretch;
// add grid line to show columns border
g.ShowGridLines = true;
ColumnDefinition columnDefinitionForPath = new ColumnDefinition();
columnDefinitionForPath.Width = new GridLength(4, GridUnitType.Star);
ColumnDefinition columnDefinitionForEmpty = new ColumnDefinition();
columnDefinitionForEmpty.Width = new GridLength(6, GridUnitType.Star);
g.ColumnDefinitions.Add(columnDefinitionForPath);
g.ColumnDefinitions.Add(columnDefinitionForEmpty);
var p1 = new Path();
p1.Stroke = new SolidColorBrush(Colors.Blue);
p1.StrokeThickness = 2;
p1.Stretch = Stretch.Fill;
p1.HorizontalAlignment = HorizontalAlignment.Stretch;
g.Children.Add(p1);
Grid.SetColumn(p1, 0);
var b1 = new Binding
{
Source = "M 10,100 C 10,300 300,-200 300,100"
};
BindingOperations.SetBinding(p1, Path.DataProperty, b1);
// you dont necessarily need this here, you can keep it empty like before
/*
var gridChild = new Grid();
g.Children.Add(gridChild);
Grid.SetColumn(gridChild, 1);
*/
return g;
}
}
UserControl XAML:
<UserControl x:Class="SilverlightApplication4.SilverlightControl3"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<Grid x:Name="objectVisual" />
</Grid>
</UserControl>
MainWindow:
<UserControl x:Class="SilverlightApplication4.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:local="clr-namespace:SilverlightApplication4"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot">
<local:SilverlightControl3 />
</Grid>
</UserControl>
I just want to display data dynamically,so i used the C# code,
TextBlock[] Cp = new TextBlock[ContactPersons.Count];
int i=0;
foreach(var item in ContactPersons)
{
Cp[i] = new TextBlock();
Cp[i].Margin = new Thickness(0,y_coordinateStart ,0,0);
Cp[i].Foreground = new SolidColorBrush(Colors.Green);
Cp[i].Visibility = Visibility.Visible;
Cp[i].Height = 30;
Cp[i].Width = 300;
y_coordinateStart += 35;
Cp[i].Text = item.firsName;
i++;
}
But nothing appears in my page.
What could be the problem??
You need to add them to the visual tree somehow... For instance
In your XAML:
<Window x:Class="MyClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<StackPanel x:Name="MainPanel" />
</Window>
In your code:
foreach(var item in ContactPersons)
{
TextBlock tb = new TextBlock();
tb.Margin = new Thickness(0,y_coordinateStart ,0,0);
tb.Foreground = new SolidColorBrush(Colors.Green);
tb.Visibility = Visibility.Visible;
tb.Height = 30;
tb.Width = 300;
y_coordinateStart += 35;
tb.Text = item.firsName;
MainPanel.Children.Add(tb);
}
I've got this UserControl defined in XAML and would like to set the ItemsPanelTemplate dynamically in my code behind class (not in the XAML like in the example):
<UserControl>
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid> <!-- I want to add this Grid definition in code behind -->
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</UserControl>
I tried something like
this.Items.ItemsPanel.Template = new Grid();
but failed miserably. Any help?
Background:
I only know the number of grid columns and rows at runtime.
You can do as you want by creating MannualCode in code behind as:
1. Create a Method as following which will return a ItemsPanelTemplate
private ItemsPanelTemplate GetItemsPanelTemplate()
{
string xaml = #"<ItemsPanelTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>";
return XamlReader.Parse(xaml) as ItemsPanelTemplate;
}
Now add this template in your Listbox ItemsPanel as:
MyListBox.ItemsPanel = GetItemsPanelTemplate();
This is working fine for me. Hope this will help.
Keep Coding....:)
You need to create an ItemsPanelTemplate and set it's VisualTree to a FrameworkElementFactory (deprecated) which creates the Grid, or use the XamlReader to parse a XAML-string which specifies the template.
This question contains usage examples of both methods (albeit for a different template property).
An easier method to manipulate the panel at runtime is outlined in this question.
In case that you still have some work to do with the elements, you should take the following (extended) code:
First we need a helper in order to get the element:
// --------------------------------------------------------------------
// This function fetches the WrapPanel from oVisual.
private WrapPanel m_FetchWrapPanel (Visual oVisual)
{
// WrapPanel to be returned
WrapPanel oWrapPanel = null;
// number of childs of oVisual
int iNumberChilds = VisualTreeHelper.GetChildrenCount (oVisual);
// and running through the childs
int i = 0;
while ( ( i < iNumberChilds ) && ( oWrapPanel == null ) )
{ // fetching visual
Visual oVisualChild =
( VisualTreeHelper.GetChild (oVisual, i) as Visual );
if ( ( oVisualChild is WrapPanel ) is true )
{ // found
oWrapPanel = ( oVisualChild as WrapPanel );
}
else
{ // checking the childs of oVisualChild
oWrapPanel = m_FetchWrapPanel (oVisualChild);
};
// checking next child
i++;
};
// returning WrapPanel
return (oWrapPanel);
}
Now we create the Panel (or something):
// --------------------------------------------------------------------
private void m_SettingTemplate ()
{
// the online doc recommends to parse the template
string xaml =
#"<ItemsPanelTemplate
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<WrapPanel ItemWidth=""150"" MaxWidth=""150""/>
</ItemsPanelTemplate>";
// assigning the template
oMyListView.ItemsPanel = ( System.Windows.Markup.XamlReader.Parse (xaml) as ItemsPanelTemplate );
// fetching the WrapPanel
WrapPanel oWrapPanel = m_WrapPanelAusVisualHolen (oMyListView);
Debug.Assert (oWrapPanel != null);
if ( oWrapPanel != null )
{ // adjusting the size of the WrapPanel to the ListView
Binding oBinding = new Binding ("ActualWidth");
oBinding.Source = oMyListView;
oWrapPanel.SetBinding (WrapPanel.MaxWidthProperty, oBinding);
};
}
Here's a XAML based program which uses ItemsPanelTemplate with a Grid:
MainWindow.xaml:
<Window x:Class="WpfTutorialStatusBarGrid.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:WpfTutorialStatusBarGrid"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="0">
<TextBlock Name="lblCursorPosition" />
</StatusBarItem>
<Separator Grid.Column="1"/>
<StatusBarItem Grid.Column="2">
<TextBlock Text="c:\temp\abc.txt"/>
</StatusBarItem>
<Separator Grid.Column="3"/>
<StatusBarItem Grid.Column="4">
<ProgressBar Value="50" Width="90" Height="16"/>
</StatusBarItem>
</StatusBar>
<TextBox AcceptsReturn="True" Name="txtEditor" SelectionChanged="TxtEditor_SelectionChanged"/>
</DockPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace WpfTutorialStatusBarGrid
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TxtEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
var row = txtEditor.GetLineIndexFromCharacterIndex(txtEditor.CaretIndex);
var col = txtEditor.CaretIndex - txtEditor.GetCharacterIndexFromLineIndex(row);
lblCursorPosition.Text = $"Line {row + 1}, Char {col + 1}";
}
}
}
It's a simple text editor with a status bar:
Here's the equivalent program with the code in C# instead of XAML:
MainWindow.xaml:
<Window x:Class="WpfTutorialStatusBarGridCs.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:WpfTutorialStatusBarGridCs"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace WpfTutorialStatusBarGridCs
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dock_panel = new DockPanel();
Content = dock_panel;
var status_bar = new StatusBar();
dock_panel.Children.Add(status_bar);
DockPanel.SetDock(status_bar, Dock.Bottom);
var items_panel_template = new ItemsPanelTemplate();
{
var grid_factory = new FrameworkElementFactory(typeof(Grid));
{
{
var col = new FrameworkElementFactory(typeof(ColumnDefinition));
col.SetValue(ColumnDefinition.WidthProperty, new GridLength(100));
grid_factory.AppendChild(col);
}
{
var col = new FrameworkElementFactory(typeof(ColumnDefinition));
col.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));
grid_factory.AppendChild(col);
}
{
var col = new FrameworkElementFactory(typeof(ColumnDefinition));
col.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star));
grid_factory.AppendChild(col);
}
{
var col = new FrameworkElementFactory(typeof(ColumnDefinition));
col.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));
grid_factory.AppendChild(col);
}
{
var col = new FrameworkElementFactory(typeof(ColumnDefinition));
col.SetValue(ColumnDefinition.WidthProperty, new GridLength(100));
grid_factory.AppendChild(col);
}
}
items_panel_template.VisualTree = grid_factory;
}
status_bar.ItemsPanel = items_panel_template;
var text_block = new TextBlock();
{
var status_bar_item = new StatusBarItem();
Grid.SetColumn(status_bar_item, 0);
status_bar_item.Content = text_block;
status_bar.Items.Add(status_bar_item);
}
{
var separator = new Separator();
Grid.SetColumn(separator, 1);
status_bar.Items.Add(separator);
}
{
var status_bar_item = new StatusBarItem();
Grid.SetColumn(status_bar_item, 2);
status_bar_item.Content = new TextBlock() { Text = "abc" };
status_bar.Items.Add(status_bar_item);
}
{
var separator = new Separator();
Grid.SetColumn(separator, 3);
status_bar.Items.Add(separator);
}
{
var status_bar_item = new StatusBarItem();
Grid.SetColumn(status_bar_item, 4);
status_bar_item.Content = new ProgressBar() { Value = 50, Width = 90, Height = 16 };
status_bar.Items.Add(status_bar_item);
}
{
var text_box = new TextBox() { AcceptsReturn = true };
text_box.SelectionChanged += (sender, e) =>
{
var row = text_box.GetLineIndexFromCharacterIndex(text_box.CaretIndex);
var col = text_box.CaretIndex - text_box.GetCharacterIndexFromLineIndex(row);
text_block.Text = $"Line {row + 1}, Char {col + 1}";
};
dock_panel.Children.Add(text_box);
}
}
}
}
The C# version is much more verbose. However, with the help of some extension methods, it can be written in a fluent style, eliminating the intermediate variables:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var text_block = new TextBlock();
Content = new DockPanel()
.AddChildren(
new StatusBar()
.SetDock(Dock.Bottom)
.SetItemsPanel(
new ItemsPanelTemplate()
.SetVisualTree(
new FrameworkElementFactory(typeof(Grid))
.AppendChildren(
new FrameworkElementFactory(typeof(ColumnDefinition))
.SetValue_(ColumnDefinition.WidthProperty, new GridLength(100)),
new FrameworkElementFactory(typeof(ColumnDefinition))
.SetValue_(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto)),
new FrameworkElementFactory(typeof(ColumnDefinition))
.SetValue_(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star)),
new FrameworkElementFactory(typeof(ColumnDefinition))
.SetValue_(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto)),
new FrameworkElementFactory(typeof(ColumnDefinition))
.SetValue_(ColumnDefinition.WidthProperty, new GridLength(100)))))
.AddItems(
new StatusBarItem() { Content = text_block }.SetColumn(0),
new Separator().SetColumn(1),
new StatusBarItem() { Content = new TextBlock() { Text = "abc" } }.SetColumn(2),
new Separator().SetColumn(3),
new StatusBarItem() { Content = new ProgressBar() { Value = 50, Width = 90, Height = 16 } }.SetColumn(4)),
new TextBox() { AcceptsReturn = true }
.AddSelectionChanged(
(sender, e) =>
{
var box = sender as TextBox;
var row = box.GetLineIndexFromCharacterIndex(box.CaretIndex);
var col = box.CaretIndex - box.GetCharacterIndexFromLineIndex(row);
text_block.Text = $"Line {row + 1}, Char {col + 1}";
}));
}
}
Here are the extension methods used:
public static class Extensions
{
public static T SetDock<T>(this T element, Dock dock) where T : UIElement
{
DockPanel.SetDock(element, dock);
return element;
}
public static T SetColumn<T>(this T element, int value) where T : UIElement
{
Grid.SetColumn(element, value);
return element;
}
public static T SetValue_<T>(this T factory, DependencyProperty dp, object value) where T : FrameworkElementFactory
{
factory.SetValue(dp, value);
return factory;
}
public static T AppendChildren<T>(this T factory, params FrameworkElementFactory[] children) where T : FrameworkElementFactory
{
foreach (var child in children)
factory.AppendChild(child);
return factory;
}
public static T SetVisualTree<T>(this T template, FrameworkElementFactory factory) where T : FrameworkTemplate
{
template.VisualTree = factory;
return template;
}
public static T1 SetItemsPanel<T1,T2>(this T1 control, T2 template) where T1 : ItemsControl where T2 : ItemsPanelTemplate
{
control.ItemsPanel = template;
return control;
}
public static T AddItems<T>(this T control, params object[] items) where T : ItemsControl
{
foreach (var item in items)
control.Items.Add(item);
return control;
}
public static T AddSelectionChanged<T>(this T obj, RoutedEventHandler handler) where T : TextBoxBase
{
obj.SelectionChanged += handler;
return obj;
}
public static T1 AddChildren<T1>(this T1 panel, params UIElement[] elements) where T1 : Panel
{
foreach (var elt in elements)
panel.Children.Add(elt);
return panel;
}
}
For anyone else...
... A control based on the ItemsControl which has an Orientation property.
It uses the FrameworkElementFactory as in the previous answers:
public class OrientationItemsControl : ItemsControl
{
public static readonly DependencyProperty OrientationProperty = WrapPanel.OrientationProperty.AddOwner(typeof(OrientationItemsControl), new PropertyMetadata(Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ItemsControl itemsControl && e.NewValue is Orientation orientation)
{
var factory = new FrameworkElementFactory(typeof(StackPanel));
factory.SetValue(OrientationProperty, orientation);
itemsControl.ItemsPanel = TemplateGenerator.CreateItemsPanelTemplate(factory);
}
}
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
}