I recently asked this question (
And here is one of the rows, after pressing Edit
:
I've used code for a similar form, but can't seem to even get anything to display. How would I go about creating this?
Here is what I have so far:
DescriptionInfoControl.xaml and DescriptionInfoControl.xaml.cs
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:desc="clr-namespace:StagingApp.Controls.Library.Custom"
xmlns:converter="clr-namespace:StagingApp.Controls.Library.Converters"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:cal="http://www.caliburnproject.org">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.TextBlocks.xaml" />
<ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.TextBoxes.xaml" />
<ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.Buttons.xaml" />
</ResourceDictionary.MergedDictionaries>
<converter:BoolToVisConverter x:Key="BoolToVisConverter" />
<Style TargetType="{x:Type desc:DescriptionInfoControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type desc:DescriptionInfoControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="DescriptionsColumn" />
<ColumnDefinition />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding DescriptionSource.Description, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Style="{StaticResource DeviceInfoPropertyTextStyle}" />
<TextBox
x:Name="PART_TextBox"
Grid.Column="1"
HorizontalContentAlignment="Center"
Style="{StaticResource DeviceInfoTextBoxStyle}" />
<Grid
Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Style="{StaticResource EditButtonStackPanelStyle}"
Grid.Column="0">
<Button
Visibility="{Binding DescriptionSource.BindingName, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}"
Style="{StaticResource DeviceInfoEditButtonStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Edit">
<cal:Parameter Value="{Binding DescriptionSource.Property.Name}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
Edit
</Button>
</StackPanel>
<StackPanel
Style="{StaticResource EditButtonStackPanelStyle}"
Grid.Column="0"
Visibility="{Binding DescriptionSource.BindingName, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}">
<Button
Style="{StaticResource DeviceInfoEditOkButtonStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="OkEdit">
<cal:Parameter Value="{Binding DescriptionSource.Property.Name}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
OK
</Button>
<Button
Style="{StaticResource DeviceInfoEditCancelButtonStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="CancelEdit">
<cal:Parameter Value="{Binding DescriptionSource.Property.Name}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
CANCEL
</Button>
</StackPanel>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
namespace StagingApp.Controls.Library.Custom;
[TemplatePart(Name = _textBoxTemplateName, Type = typeof(TextBox))]
public class DescriptionInfoControl : Control
{
private const string _textBoxTemplateName = "PART_TextBox";
private TextBox? _partTextBox;
private Binding? _textBinding;
public override void OnApplyTemplate()
{
_partTextBox = GetTemplateChild(_textBoxTemplateName) as TextBox;
SetBindingPartTextbox();
}
private void SetBindingPartTextbox()
{
if (_partTextBox is TextBox tbox)
{
if (_textBinding is null)
{
BindingOperations.ClearBinding(tbox, TextBox.TextProperty);
}
else
{
tbox.SetBinding(TextBox.TextProperty, _textBinding);
}
}
}
static DescriptionInfoControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(DescriptionInfoControl),
new FrameworkPropertyMetadata(typeof(DescriptionInfoControl)));
}
public DescriptionDto DescriptionSource
{
get => (DescriptionDto)GetValue(DescriptionSourceProperty);
set => SetValue(DescriptionSourceProperty, value);
}
// Using a DependencyProperty as the backing store for Description. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DescriptionSourceProperty =
DependencyProperty.Register(
nameof(DescriptionSource),
typeof(DescriptionDto),
typeof(DescriptionInfoControl),
new PropertyMetadata(null, DescriptionSourceChangedCallback));
private static readonly PropertyPath _newValuePropertyPath =
new(typeof(DescriptionDto).GetProperty(nameof(DescriptionDto.NewValue)));
private static void DescriptionSourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DescriptionInfoControl control = (DescriptionInfoControl)d;
Binding? binding = null;
if (e.NewValue is DescriptionDto description)
{
binding = new Binding
{
Path = _newValuePropertyPath,
Source = description.Source,
Mode = BindingMode.TwoWay
};
}
control._textBinding = binding;
control.SetBindingPartTextbox();
}
}
DescriptionsInfoListControl.xaml and DescriptionsInfoListControl.xaml.cs
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:desc="clr-namespace:StagingApp.Controls.Library.Custom"
xmlns:models="clr-namespace:StagingApp.Controls.Library.Models">
<Style TargetType="{x:Type desc:DescriptionsInfoListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type desc:DescriptionsInfoListControl}">
<ItemsControl
ItemsSource="{TemplateBinding Descriptions}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate
DataType="{x:Type models:DescriptionDto}">
<desc:DescriptionInfoControl
DescriptionSource="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
namespace StagingApp.Controls.Library.Custom;
public class DescriptionsInfoListControl : Control
{
static DescriptionsInfoListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(DescriptionsInfoListControl),
new FrameworkPropertyMetadata(typeof(DescriptionsInfoListControl)));
}
public ReadOnlyCollection<DescriptionDto> Descriptions
{
get => (ReadOnlyCollection<DescriptionDto>)GetValue(DescriptionsProperty);
set { SetValue(_descriptionsPropertyKey, value); }
}
private static readonly ReadOnlyCollection<DescriptionDto> _descriptionsEmpty = Array.AsReadOnly(Array.Empty<DescriptionDto>());
private static readonly DependencyPropertyKey _descriptionsPropertyKey =
DependencyProperty.RegisterReadOnly(
nameof(Descriptions),
typeof(ReadOnlyCollection<DescriptionDto>),
typeof(DescriptionsInfoListControl),
new PropertyMetadata(_descriptionsEmpty));
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DescriptionsProperty = _descriptionsPropertyKey.DependencyProperty;
public DescriptionsInfoListControl()
{
DataContextChanged = OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ReadOnlyCollection<DescriptionDto> descriptions;
if (e.NewValue is null)
{
descriptions = _descriptionsEmpty;
}
else
{
descriptions = DescriptionPropertyList.GetDescriptions(e.NewValue);
}
Descriptions = descriptions;
}
}
For the full code, please look at https://github.com/hmsiegel/StagingApp
Any help in getting these InfoControls and InfoViews to work and display would be great.
Thanks
CodePudding user response:
I added methods to remember the entered value and reset the entered value to DescriptionDto.
namespace StagingApp.Controls.Library.Models;
public class DescriptionDto : INotifyPropertyChanged
{
public DescriptionDto(string? description,
PropertyInfo property,
object? source)
{
if (property.GetGetMethod() is not MethodInfo method)
{
throw new ArgumentException("Property must have a public getter.", nameof(property));
}
if (!method.IsStatic)
{
if (source is null)
throw new ArgumentException("For Source=null, Property must be static.", nameof(property));
if (property != source.GetType().GetProperty(property.Name))
throw new ArgumentException("This property is not from this Source.", nameof(property));
}
Description = description ?? string.Empty;
Property = property;
Source = source;
RefreshNewValue();
}
public string? Description { get; }
public PropertyInfo Property { get; }
public object? Source { get; }
private string? _newValue;
public string? NewValue
{
get => _newValue;
set
{
_newValue = value;
PropertyChanged?.Invoke(this, NewValueEventArgs);
}
}
private static readonly PropertyChangedEventArgs NewValueEventArgs = new PropertyChangedEventArgs(nameof(NewValue));
public event PropertyChangedEventHandler? PropertyChanged;
public DescriptionDto SetSource(object? newSource) =>
new(Description!, Property, newSource);
public override string ToString() =>
$"{(string.IsNullOrWhiteSpace(Description) ? string.Empty : $"[{Description}] ")}({Source?.GetType().Name}).{Property.Name}: {NewValue}";
public void RefreshNewValue()
{
NewValue = Property.GetValue(Source)?.ToString();
}
public void UpdateProperty()
{
Property.SetValue(Source, NewValue);
}
}
I added commands for the row buttons to the DescriptionControl.
using System.Windows.Input;
namespace StagingApp.Controls.Library.Custom;
public partial class DescriptionControl : Control
{
/// <summary>Sets the DescriptionControl to edit mode: <see cref="DescriptionControl.IsReadOnly"/> = <see langword="false"/>.</summary>
public static RoutedUICommand Edit { get; } = new RoutedUICommand("Go to edit mode.", nameof(Edit), typeof(DescriptionControl));
/// <summary>Updates the value of the source property <see cref="DescriptionDto.Property"/> with the value received
/// from the input field <see cref="DescriptionDto.NewValue"/>.</summary>
public static RoutedUICommand OK { get; } = new RoutedUICommand("Accept changes.", nameof(OK), typeof(DescriptionControl));
/// <summary>Returns the value of the input field <see cref="DescriptionDto.NewValue"/>
/// to the value of the source property <see cref="DescriptionDto.Property"/>
/// and cancels the edit mode: <see cref="DescriptionControl.IsReadOnly"/> = <see langword="true"/>.</summary>
public static RoutedUICommand Cancel { get; } = new RoutedUICommand("Undo changes and edit mode.", nameof(Cancel), typeof(DescriptionControl));
}
using System.Windows.Input;
namespace StagingApp.Controls.Library.Custom;
public partial class DescriptionControl : Control
{
public DescriptionControl()
{
ProtectedIsReadOnly = IsReadOnly;
// Initializing a Routed Commands Binding.
CommandBinding editCommand = new CommandBinding() { Command = Edit };
editCommand.CanExecute = OnCanExecute;
editCommand.Executed = OnExecuted;
CommandBinding cancelCommand = new CommandBinding() { Command = Cancel };
cancelCommand.CanExecute = OnCanExecute;
cancelCommand.Executed = OnExecuted;
CommandBinding OkCommand = new CommandBinding() { Command = OK };
OkCommand.CanExecute = OnCanExecute;
OkCommand.Executed = OnExecuted;
// Save a Routed Commands Binding.
CommandBindings.Add(editCommand);
CommandBindings.Add(cancelCommand);
CommandBindings.Add(OkCommand);
}
private void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Command == Edit)
{
e.CanExecute = ProtectedIsReadOnly;
}
else if (e.Command == Cancel || e.Command == OK)
{
e.CanExecute = !ProtectedIsReadOnly;
}
}
private void OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
if (e.Command == Edit)
{
IsReadOnly = false;
}
else if (e.Command == Cancel || e.Command == OK)
{
if (e.Command == Cancel)
{
IsReadOnly = true;
ProtectedDescriptionSource?.RefreshNewValue();
if (_partTextBox is not null)
{
BindingOperations.GetBindingExpressionBase(_partTextBox, TextBox.TextProperty)
?.UpdateTarget();
}
}
else
{
ProtectedDescriptionSource?.UpdateProperty();
}
}
}
}
I added buttons for the row to the DescriptionControl Template. I did not style them - do it yourself as you like.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:desc="clr-namespace:StagingApp.Controls.Library.Custom">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.TextBlocks.xaml" />
<ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.TextBoxes.xaml" />
</ResourceDictionary.MergedDictionaries>
<BooleanToVisibilityConverter x:Key="booleanToVisibility"/>
<Style TargetType="{x:Type desc:DescriptionControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type desc:DescriptionControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="DescriptionsColumn"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding DescriptionSource.Description, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Style="{StaticResource ConfigureTextBlockStyle}"/>
<TextBox x:Name="PART_TextBox" Grid.Column="1"
Style="{StaticResource ConfigureTextBox}"
IsReadOnly="{TemplateBinding IsReadOnly}"/>
<Button Grid.Column="2" Padding="15 5"
Command="{x:Static desc:DescriptionControl.Edit}"
Content="{Binding Command.Name, RelativeSource={RelativeSource Self}}"
ToolTip="{Binding Command.Text, RelativeSource={RelativeSource Self}}"
Visibility="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={StaticResource booleanToVisibility}}"/>
<Button Grid.Column="2" Padding="15 5"
Command="{x:Static desc:DescriptionControl.OK}"
Content="{Binding Command.Name, RelativeSource={RelativeSource Self}}"
ToolTip="{Binding Command.Text, RelativeSource={RelativeSource Self}}"
Visibility="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={StaticResource booleanToVisibility}}"/>
<Button Grid.Column="3" Padding="15 5"
Command="{x:Static desc:DescriptionControl.Cancel}"
Content="{Binding Command.Name, RelativeSource={RelativeSource Self}}"
ToolTip="{Binding Command.Text, RelativeSource={RelativeSource Self}}"
Visibility="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={StaticResource booleanToVisibility}}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Fixed a few more bugs. I will not describe them here. Pay attention to getting the filename correctly (Bootstrapper.cs):
public const string SettingsFileName = "appsettings.json";
public static readonly string SettingsFileFullName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, SettingsFileName);
private static IConfiguration AddConfiguration()
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(SettingsFileFullName, false, false);
return builder.Build();
}
I committed all the changes to the eldhasp branch.