WPF A good way to do control over viewing / editing?

This is just a discussion point - what's the best way to do viewing / editing in WPF? For instance. we have an object of the Person object that has the requisite (name, surname, address, phone, etc.). One presentation of the control will be a read-only view. And the other will have the form of editing for the same person. Example:

<UserControl x:Name="MyPersonEditor"> <Grid> <Grid x:Name="ViewGrid" Visibility="Visible"> <TextBlock Text="Name:"/> <TextBlock Text="{Binding Person.Name}"/> <Button Content="Edit" Click="ButtonEditStart_Click"/> </Grid> <Grid x:Name="EditGrid" Visibility="Collapsed"> <TextBlock Text="Name:"/> <TextBox Text="{Binding Person.Name}"/> <Button Content="Save" Click="ButtonEditEnd_Click"/> </Grid> </Grid> </UserControl> 

I hope the idea is clear. Two options that I see right now

  • two grids with switching visibility and
  • TabControl without title bar

This is just a debatable question - there are not many problems with this, but I am just wondering if there are other possibilities and elegant solutions for this.

+4
source share
4 answers

Auto Lock Class

I wrote the "AutomaticLock" class, which has the inherited "DoLock" property attached.

Setting the "DoLock" property to true re-templates for all text fields of ComboBoxes, CheckBoxes, etc., to be text blocks, not editable CheckBoxes, etc. My code is set up so that another attached property can specify an arbitrary pattern for use in blocking mode ("view"), controls that should never be automatically locked, etc.

Thus, the same view can be easily used both for editing and for viewing. Setting one property changes it back and forth, and it is fully customizable, since any control in the view can run the DoLock property to change its appearance or behavior arbitrarily.

Implementation code

Here is the code:

 public class AutomaticLock : DependencyObject { Control _target; ControlTemplate _originalTemplate; // AutomaticLock.Enabled: Set true on individual controls to enable locking functionality on that control public static bool GetEnabled(DependencyObject obj) { return (bool)obj.GetValue(EnabledProperty); } public static void SetEnabled(DependencyObject obj, bool value) { obj.SetValue(EnabledProperty, value); } public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(AutomaticLock), new FrameworkPropertyMetadata { PropertyChangedCallback = OnLockingStateChanged, }); // AutomaticLock.LockTemplate: Set to a custom ControlTemplate to be used when control is locked public static ControlTemplate GetLockTemplate(DependencyObject obj) { return (ControlTemplate)obj.GetValue(LockTemplateProperty); } public static void SetLockTemplate(DependencyObject obj, ControlTemplate value) { obj.SetValue(LockTemplateProperty, value); } public static readonly DependencyProperty LockTemplateProperty = DependencyProperty.RegisterAttached("LockTemplate", typeof(ControlTemplate), typeof(AutomaticLock), new FrameworkPropertyMetadata { PropertyChangedCallback = OnLockingStateChanged, }); // AutomaticLock.DoLock: Set on container to cause all children with AutomaticLock.Enabled to lock public static bool GetDoLock(DependencyObject obj) { return (bool)obj.GetValue(DoLockProperty); } public static void SetDoLock(DependencyObject obj, bool value) { obj.SetValue(DoLockProperty, value); } public static readonly DependencyProperty DoLockProperty = DependencyProperty.RegisterAttached("DoLock", typeof(bool), typeof(ControlTemplate), new FrameworkPropertyMetadata { Inherits = true, PropertyChangedCallback = OnLockingStateChanged, }); // CurrentLock: Used internally to maintain lock state [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public static AutomaticLock GetCurrentLock(DependencyObject obj) { return (AutomaticLock)obj.GetValue(CurrentLockProperty); } public static void SetCurrentLock(DependencyObject obj, AutomaticLock value) { obj.SetValue(CurrentLockProperty, value); } public static readonly DependencyProperty CurrentLockProperty = DependencyProperty.RegisterAttached("CurrentLock", typeof(AutomaticLock), typeof(AutomaticLock)); static void OnLockingStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { AutomaticLock current = GetCurrentLock(obj); bool shouldLock = GetDoLock(obj) && (GetEnabled(obj) || GetLockTemplate(obj)!=null); if(shouldLock && current==null) { if(!(obj is Control)) throw new InvalidOperationException("AutomaticLock can only be used on objects derived from Control"); new AutomaticLock((Control)obj).Attach(); } else if(!shouldLock && current!=null) current.Detach(); } AutomaticLock(Control target) { _target = target; } void Attach() { _originalTemplate = _target.Template; _target.Template = GetLockTemplate(_target) ?? SelectDefaultLockTemplate(); SetCurrentLock(_target, this); } void Detach() { _target.Template = _originalTemplate; _originalTemplate = null; SetCurrentLock(_target, null); } ControlTemplate SelectDefaultLockTemplate() { for(Type type = _target.GetType(); type!=typeof(object); type = type.BaseType) { ControlTemplate result = _target.TryFindResource(new ComponentResourceKey(type, "AutomaticLockTemplate")) as ControlTemplate ?? _target.TryFindResource(new ComponentResourceKey(typeof(AutomaticLock), type.Name)) as ControlTemplate; if(result!=null) return result; } return null; } } 

This code will allow you to specify an automatic lock pattern based on controls individually or it will allow you to use the default patterns defined either in the assembly containing the AutomaticLock class in the assembly containing your custom control, which lock pattern applies to your local resources in your visual tree (including application resources)

How to identify AutomaticLock patterns

The default templates for standard WPF controls are defined in the assembly containing the AutomaticLock class in the ResourceDictionary, combined with Themes / Generic.xaml. For example, this pattern causes all TextBoxs to turn into TextBlocks when locked:

 <ControlTemplate TargetType="{x:Type TextBox}" x:Key="{ComponentResourceKey ResourceId=TextBox, TypeInTargetAssembly={x:Type lc:AutomaticLock}}"> <TextBlock Text="{TemplateBinding Text}" /> </ControlTemplate> 

The default templates for custom controls can be defined in the assembly containing the user control in the ResourceDictionary, placed in its Themes / Generic.xaml. In this case, the ComponentResourceKey component is different, for example:

 <ControlTemplate TargetType="{x:Type prefix:MyType}" x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}"> ... 

If an application wants to override the default AutomaticLock pattern for a specific type, it can put the auto-lock pattern in its App.xaml, Window XAML, UserControl XAML, or in the ResourceDictionary of a separate control. In each case, the ComponentResourceKey should be specified in the same way as for custom controls:

 x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}" 

Finally, an automatic lock pattern can be applied to a single control by setting its AutomaticLock.LockTemplate property.

How to use AutomaticLock in the user interface

To use auto-lock:

  • Set AutomaticLock.Enabled = "True" to any controls that should be automatically locked. This can be done in style or directly on individual controls. It allows you to lock the control, but does not cause the control to lock.
  • If you want to lock, set AutomaticLock.DoLock = "True" to your top-level control (Window, view, UserControl, etc.) when you want automatic locking to actually be performed. You can bind AutomaticLock.DoLock to a checkbox or menu item, or you can control it in code.

Some tips for effectively switching viewing and editing modes

This AutomaticLock class is great for switching between viewing and editing modes, even if they are significantly different. I have several different methods for constructing my views in order to accommodate differences in layout during editing. Some of them:

  • Make controls invisible in edit or view mode by setting either their template or AutomaticLockTemplate to an empty template, depending on the situation. For example, suppose that "Age" is at the top of your layout in view mode and at the bottom in edit mode. Add a TextBox for "Age" in both places. An empty template is installed in the top template set, so it is not displayed in edit mode. At the bottom, set AutomaticLockTemplate to an empty template. Now only one will be displayed.

  • Use ContentControl to replace borders, layout panels, buttons, etc. without affecting the content. The ContentControl template has surrounding borders, panels, buttons, etc. For edit mode. It also has an AutomaticLockTemplate, which has a view mode version.

  • Use the control to replace the rectangular section of your view. (By this, I really mean the object of the Control class, not a subclass of therof.) Again, you put the version of the edit mode in the template and the version of the view in AutomaticLockTemplate.

  • Use a grid with extra rows and columns with automatic size. Use the trigger of the AutomaticLock.DoLock property to update the Row, Column, RowSpan and ColumnSpan properties of elements in the Grid. For example, you can move the panel containing the Age control at the top by changing its Grid.Row value from 6 to 0.

  • A DoLock trigger to apply a LayoutTranform or RenderTransform to your elements or to set other properties, such as Width and Height. This is useful if you want things to be bigger in edit mode, or if you want to make the TextBox wider and move the button next to it relative to the edge.

Please note that you can use option No. 3 (control object with separate templates for editing and viewing modes) for the entire view. This would be done if the editing and viewing modes were completely different. In this case, AutomaticLock still gives you the opportunity to install two templates manually. It will look like this:

 <Control> <Control.Template> <ControlTemplate> <!-- Edit mode view here --> </ControlTemplate> </Control.Template> <lib:AutomaticLock.LockTemplate> <ControlTemplate> <!-- View mode view here --> </ControlTemplate> </lib:AutomaticLock.LockTemplate> </Control> 

It is usually easier to set up a few small positions and things between the editing and viewing modes and is better for your user, because the user will have a consistent layout, but if you need a complete replacement, then AutomaticLock gives you such power as well.

+3
source
 <Grid> <TextBlock Text="Name:"/> <LabelText="{Binding Person.Name}" Cursor="IBeam" MouseDoubleClick="lblName_dblClick"/> <!-- set the IsEditMode to true inside this event --> <TextBox Text="{Binding Person.Name}" Visibility="{Binding IsEditMode, Converter={StaticResource BoolToVisConverter}}"/> <Button Content="OK" Click="btnSave_Click" Visibility="{Binding IsEditMode, Converter={StaticResource BoolToVisConverter}}"/> <!-- set the IsEditMode to false inside this event --> </Grid> 

Use the command if you are familiar with it.

+1
source

I would create one view with two different configurations, i.e. 2 different constructors to make the corresponding field editable / readonly or visible / hidden

This way you are not writing redundant XAML, and you can configure all the fields above the code behind or the ViewModel when using MVVM

0
source

Sounds like work for a DataTemplateSelector to me. If you would prefer to switch individual controls, I would do something similar to what Veer suggested.

0
source

Source: https://habr.com/ru/post/1312834/


All Articles