Your Custom Control can be something like this:
class SoftwareInstallStatus_Ctrl : Grid
{
Button button;
ProgressBar progress;
RadioButton radio;
public SoftwareInstallStatus_Ctrl() {
button = new Button();
progress = new ProgressBar() { Margin = new Thickness(5,0,5,0)};
radio = new RadioButton();
SetColumn(progress, 1);
SetColumn(radio, 2);
ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(150) });
ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
Children.Add(button);
Children.Add(progress);
Children.Add(radio);
}
protected override void OnInitialized(EventArgs e) {
button.SetBinding(Button.ContentProperty, new Binding(nameof(ButtonContent)) { Source = this });
progress.SetBinding(ProgressBar.ValueProperty, new Binding(nameof(ProgressValue)) { Source = this });
radio.SetBinding(RadioButton.IsCheckedProperty, new Binding(nameof(IsChecked)) { Source = this });
}
public string ButtonContent {
get { return (string)GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
public double ProgressValue {
get { return (double)GetValue(ProgressValueProperty); }
set { SetValue(ProgressValueProperty, value); }
}
public bool IsChecked {
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(SoftwareInstallStatus_Ctrl), new PropertyMetadata(false));
public static readonly DependencyProperty ProgressValueProperty =
DependencyProperty.Register("ProgressValue", typeof(double), typeof(SoftwareInstallStatus_Ctrl), new PropertyMetadata(0d));
public static readonly DependencyProperty ButtonContentProperty =
DependencyProperty.Register("ButtonContent", typeof(string), typeof(SoftwareInstallStatus_Ctrl), new PropertyMetadata(null));
}
With this approach you don't need anything else for Custom control. In your MainWindow.xaml
, you can have these:
<Grid Margin="20">
<ItemsControl ItemsSource="{Binding Software}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:SoftwareInstallStatus_Ctrl
ButtonContent="{Binding Name}"
ProgressValue="{Binding Progress}"
IsChecked="{Binding IsComplete}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and in MainWindow.xaml.cs
these:
public partial class MainWindow : Window
{
public List<Soft> Software { get; set; }
public MainWindow() {
InitializeComponent();
Software = new List<Soft>();
Software.Add(new Soft() { Name = "Rent Manager", Progress = 100, IsComplete = true });
Software.Add(new Soft() { Name = "Stock Trader", Progress = 75, IsComplete = false });
Software.Add(new Soft() { Name = "Something Else", Progress = 80, IsComplete = false });
DataContext = this;
}
}
public class Soft
{
public string Name { get; set; }
public double Progress { get; set; }
public bool IsComplete { get; set; }
}
you'll see this when you launch the app:
In my apps, I used FrameworkElement
, ArrangeOverride
, MeasureOverride
, VisualCollection
, etc. BUT when you need some sort of container like Grid
, StackPanel
, Border
, etc. You simply could subclass the container/control and add relevant controls, dependency/normal properties and bind.
EDIT
---
For command, nowadays, I don't use ICommand
, I use Action instead. The approach with Action is add two more dependency properties in your custom control:
public Action<object> Command {
get { return (Action<object>)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter {
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(SoftwareInstallStatus_Ctrl), new PropertyMetadata(null));
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(Action<object>), typeof(SoftwareInstallStatus_Ctrl), new PropertyMetadata(null));
and override another Preview
function in your custom control:
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e) {
if(e.Source is Button)
Command.Invoke(CommandParameter);
}
and in the viewModel add an Action Property and initialize like this:
...
public Action<object> TheCommand { get; set; }
public MainWindow() {
...
TheCommand = execute;
DataContext = this;
}
void execute(object o) {
Debug.WriteLine(o);
}
and in MainWindow.xaml
Bind those like this:
<ItemsControl ItemsSource="{Binding Software}" x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:SoftwareInstallStatus_Ctrl
ButtonContent="{Binding Name}"
Command="{Binding DataContext.TheCommand, Source={x:Reference items}}"
CommandParameter="Hello"
ProgressValue="{Binding Progress}"
IsChecked="{Binding IsComplete}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It'll work. If you want to use ICommand
, make the DP a type of ICommand and use that instead, that'll work as well.
EDIT
----
For ICommand
, add these in OnInitialized
:
protected override void OnInitialized(EventArgs e) {
button.SetBinding(Button.ContentProperty, new Binding(nameof(ButtonContent)) { Source = this });
button.SetBinding(Button.CommandProperty, new Binding(nameof(Command)) { Source = this });
button.SetBinding(Button.CommandParameterProperty, new Binding(nameof(CommandParameter)) { Source = this });
progress.SetBinding(ProgressBar.ValueProperty, new Binding(nameof(ProgressValue)) { Source = this });
radio.SetBinding(RadioButton.IsCheckedProperty, new Binding(nameof(IsChecked)) { Source = this });
}
you don't have to override that Preview
function in Custom Control. In MainWindow.xaml.cs
have these:
public partial class MainWindow : Window
{
...
public ICommand TheCommand { get; set; }
public MainWindow() {
---
TheCommand = new Command(execute, (o) => true);
DataContext = this;
}
void execute(object o) {
Debug.WriteLine(o);
}
and in MainWindow.xaml
these:
<ItemsControl ItemsSource="{Binding Software}" x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:SoftwareInstallStatus_Ctrl
ButtonContent="{Binding Name}"
Command="{Binding DataContext.TheCommand, Source={x:Reference items}}"
CommandParameter="{Binding Name}"
ProgressValue="{Binding Progress}"
IsChecked="{Binding IsComplete}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>