Класс автоматической блокировки
Я написал класс «AutomaticLock», у которого есть унаследованное свойство «DoLock».
Установка свойства "DoLock" в значение true приводит к повторному шаблонированию всех текстовых полей ComboBoxes, CheckBoxes и т. Д. В TextBlocks, не редактируемые чекбоксы и т. Д. Мой код настроен так, что другое прикрепленное свойство может указывать произвольный шаблон для использования в заблокированном («просмотр») режиме, элементы управления, которые никогда не должны автоматически блокироваться, и т. Д.
Таким образом, один и тот же вид можно легко использовать как для редактирования, так и для просмотра. Установка одного свойства изменяет его вперед и назад, и оно полностью настраиваемо, потому что любой элемент управления в представлении может вызвать свойство «DoLock» для произвольного изменения его внешнего вида или поведения.
Код реализации
Вот код:
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;
}
}
Этот код позволит вам указать шаблон автоматической блокировки для каждого элемента управления или использовать шаблоны по умолчанию, определенные либо в сборке, содержащей класс AutomaticLock, в сборке, содержащей ваш пользовательский элемент управления, что Шаблон блокировки применяется к вашим локальным ресурсам в визуальном дереве (включая ресурсы приложения)
Как определить шаблоны автоматической блокировки
Шаблоны по умолчанию для стандартных элементов управления WPF определены в сборке, содержащей класс AutomaticLock в ResourceDictionary, объединенном с Themes / Generic.xaml. Например, этот шаблон заставляет все текстовые поля превращаться в текстовые блоки при блокировке:
<ControlTemplate TargetType="{x:Type TextBox}"
x:Key="{ComponentResourceKey ResourceId=TextBox, TypeInTargetAssembly={x:Type lc:AutomaticLock}}">
<TextBlock Text="{TemplateBinding Text}" />
</ControlTemplate>
Шаблоны по умолчанию для пользовательских элементов управления могут быть определены в сборке, содержащей пользовательский элемент управления в ResourceDictionary, замеренный в его Themes / Generic.xaml. ComponentResourceKey отличается в этом случае, например:
<ControlTemplate TargetType="{x:Type prefix:MyType}"
x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}">
...
Если приложение хочет переопределить стандартный шаблон AutomaticLock для определенного типа, оно может поместить шаблон автоматической блокировки в свой файл App.xaml, Window XAML, UserControl XAML или в ResourceDictionary отдельного элемента управления. В каждом случае ComponentResourceKey следует указывать так же, как и для пользовательских элементов управления:
x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}"
Наконец, шаблон автоматической блокировки можно применить к одному элементу управления, установив его свойство AutomaticLock.LockTemplate
.
Как использовать AutomaticLock в вашем интерфейсе
Для использования автоматической блокировки:
- Установите AutomaticLock.Enabled = "True" на любые элементы управления, которые должны быть автоматически заблокированы. Это может быть сделано в стиле или непосредственно на отдельных элементах управления. Включает блокировку элемента управления, но не вызывает фактической блокировки элемента управления.
- Если вы хотите заблокировать, установите AutomaticLock.DoLock = "True" в элементе управления верхнего уровня (Window, view, UserControl и т. Д.) Всякий раз, когда вы хотите, чтобы автоматическая блокировка действительно имела место. Вы можете привязать AutomaticLock.DoLock к флажку или пункту меню, или вы можете управлять им в коде.
Некоторые советы по эффективному переключению между режимами просмотра и редактирования
Этот класс AutomaticLock отлично подходит для переключения между режимами просмотра и редактирования, даже если они существенно различаются. У меня есть несколько различных методов построения моих представлений, чтобы приспособить различия макета при редактировании. Вот некоторые из них:
Сделайте элементы управления невидимыми в режиме редактирования или просмотра, установив для их Template или AutomaticLockTemplate пустой шаблон в зависимости от обстоятельств. Например, предположим, что «Возраст» находится вверху макета в режиме просмотра и внизу в режиме редактирования. Добавьте TextBox для «Возраст» в обоих местах. В верхнем из них установите для шаблона пустой шаблон, чтобы он не отображался в режиме редактирования. В нижней части установите AutomaticLockTemplate для пустого шаблона. Теперь только один будет виден одновременно.
Используйте ContentControl для замены границ, панелей макета, кнопок и т. Д. Окружающего контента, не затрагивая его. Шаблон ContentControl имеет окружающие границы, панели, кнопки и т. Д. Для режима редактирования. Он также имеет AutomaticLockTemplate с версией режима просмотра.
Используйте элемент управления для замены прямоугольного сечения вашего вида. (Под этим я на самом деле подразумеваю объект класса «Управление», а не подкласс.) Опять же, вы помещаете версию режима редактирования в шаблон, а версию режима просмотра в AutomaticLockTemplate.
Использовать сетку с дополнительными автоматическими размерами строк и столбцов. Используйте триггер для свойства AutomaticLock.DoLock, чтобы обновить свойства Row, Column, RowSpan и ColumnSpan элементов в сетке. Например, вы можете переместить панель, содержащую элемент управления «Возраст», на верх, изменив значение Grid.Row с 6 на 0.
Триггер в DoLock для применения LayoutTranform или RenderTransform к вашим элементам или для установки других свойств, таких как Ширина и Высота. Это полезно, если вы хотите, чтобы в режиме редактирования было больше, или если вы хотите сделать TextBox шире и переместить расположенную рядом кнопку к краю.
Обратите внимание, что вы можете использовать опцию № 3 (объект управления с отдельными шаблонами для режимов редактирования и просмотра) для всего представления. Это было бы сделано, если бы режимы редактирования и просмотра были совершенно разными. В этом случае AutomaticLock по-прежнему дает вам возможность установить два шаблона вручную. Это будет выглядеть так:
<Control>
<Control.Template>
<ControlTemplate>
<!-- Edit mode view here -->
</ControlTemplate>
</Control.Template>
<lib:AutomaticLock.LockTemplate>
<ControlTemplate>
<!-- View mode view here -->
</ControlTemplate>
</lib:AutomaticLock.LockTemplate>
</Control>
Как правило, проще настроить несколько небольших позиций и вещей между режимами редактирования и просмотра, и это будет удобнее для пользователя, поскольку у пользователя будет согласованная компоновка, но если вам потребуется полная замена, AutomaticLock предоставит вам такую возможность, как хорошо.