Lazy load FlipView content - wpf

I have a FlipView with 2 tabs in UWP which I think behaves the as TabControl in WPF. I am following the post provided here lazy load tab control contents by Ruben Bartelink. But my problem is when I go to the screen, I see the breakpoint hitting all the InitilizeComponent() for all the user controls. Please help.
Here is my xaml:
<FlipView
x:Name="FlipViewStatus" Grid.Row="2"
Style="{StaticResource FlipViewStyleNoPageButtons}"
Background="Transparent"
SelectedIndex="{x:Bind ViewModel.SelectedTabIndex, Mode=TwoWay}">
<FlipView.ItemContainerStyle>
<Style TargetType="FlipViewItem">
<Setter Property="Padding" Value="2 4 4 2"/>
</Style>
</FlipView.ItemContainerStyle>
<FlipViewItem
x:Name="FlipViewItemOngoing"
Content="{x:Bind ViewModel.VmOnGoingRuns,Mode=OneWay}">
<FlipViewItem.ContentTemplate>
<DataTemplate x:DataType="model:VmOnGoingRunsCollection">
<Grid x:Name="FlipViewItemOngoingGrid"
PointerWheelChanged="PointerWheelIgnore">
<local:StatusRunsView
x:Name="StatusOngoingRuns"
RecentSpecimenRuns="{x:Bind OnGoingRunsCollection,
Mode=OneWay}"
IsLoadingRuns="{x:Bind IsLoadingOngoingRuns,
Mode=OneWay}"/>
</Grid>
</DataTemplate>
</FlipViewItem.ContentTemplate>
</FlipViewItem>
<FlipViewItem
x:Name="FlipViewItemSampleOutput"
Content="{x:Bind ViewModel.OutputStatusViewModel,Mode=OneWay}">
<FlipViewItem.ContentTemplate>
<DataTemplate>
<Grid x:Name="FlipViewItemSampleOutputGrid"
PointerWheelChanged="PointerWheelIgnore">
<local:StatusSampleOutputView x:Name="StatusSampleOutput"
ViewModel="{Binding}"/>
</Grid>
</DataTemplate>
</FlipViewItem.ContentTemplate>
</FlipViewItem>
</FlipView>
In the ViewModel associated to this FlipView I have some code as follows.
public class ViewModel : BindableBaseThreadSafe
{
private ISampleOutputStatusViewModel _outputStatusViewModel;
public ISampleOutputStatusViewModel OutputStatusViewModel
{
get => _outputStatusViewModel;
set => Set(ref _outputStatusViewModel, value);
}
public int SelectedTabIndex
{
//I am debugging here and the tab index is correct
get => _selectedTabIndex;
set
{
if (Set(ref _selectedTabIndex, value))
{
LoadData((StatusList)_selectedTabIndex)
.Await(OnErrorLoadingData);
}
}
}
public ViewModel()
{
VmOnGoingRuns =
lifetimeScope.Resolve<VmOnGoingRunsCollection>();
VmOnGoingRuns.OnGoingRunsCollection =
new ObservableCollection<VmStatusSpecimen>();
}
//The listname enum is being set from xaml.
private async Task LoadData(StatusList listName)
{
switch (listName)
{
case StatusList.OngoingRuns:
await LoadOngoingRuns();
break;
// This code executes correctly when the user selects the
// Output tab in the FlipView.
case StatusList.Output:
{
OutputStatusViewModel = _lifetimeScope
.Resolve<ISampleOutputStatusViewModel>();
break;
}
}
}
}
My problem is as soon as I navigate to this FlipView page, I see the breakpoint hitting the StatusSampleOutputView.xaml.cs InitilizeComponent(). I want it to lazy load, in other words only when the user selects the StatusSampleOutputView, I want this view to load. Please help what am I doing wrong.,

FlipView control from uwp is different from the TabControl in wpf, you can’t expect it to implement your requirement like you use TabControl in wpf app. Besides, FlipView derives from ItemsControl, which has a lazy load called UI virtualization. This means that UI elements represtenting the items are created on demand.
For an items control bound to multiple items collection, when items are close to being scrolled into view (a few pages away), the framework can generate UI for these items and caches them. Therefore, you could try to create multiple items(such as 100 items), then check if the framework generates UI for all items at the same time.

ContentControl can have lazy loading, you can try following:
Wrap actual item view model into some lightweight dummy view model with single property
In the FlipView item template place ContentControl and bind it to that property
Create EmptyWhenNullTemplateSelector for ContentControl, return new DataTemplate() for null and actual data template when data is present
When selection of the FlipView changes, load data and set actual view model

Uwp has a property called x:Load on a UI element. In my specific case if I wanted to lazy load a tab, I have a bool property in the ViewModel that Implements INotifyPropertyChanged, I would switch between true or false. On the other hand if you want to set the x:Load property of a UIElement in the xaml.cs, you need to do something like this.
if (sender is FrameworkElement frameworkElement)
{
// This loads the child UI elements when the expander is expanded
frameworkElement.FindName("ListViewPanels");
}

Related

What is the best way to add new field into Model collection in ViewModel in MVVM app?

I'm writing WPF application with MVVM structure using MVVM Light.
I have class Foo in the Model:
class Foo: ObservableObject
{
private string _propA = String.Empty;
public string PropA
{
get => _propA ;
set
{
if (_propA == value)
{
return;
}
_propA = value;
RaisePropertyChanged("PropA");
}
}
// same for property PropB, PropC, PropD, etc.
}
And I have some collection of Foo objects in the Model:
class FooCollection: ObservableObject
{
private ObservableCollection<Foo> _items = null;
public IEnumerable<Foo> Items
{
get { ... }
set { ... }
}
public string Name { get; set; }
// ...
// and other methods, properties and fields
}
Now I have a ViewModel where this list is populated via some injected provider:
class MainWindowModel: ViewModelBase
{
private FooCollection _fooList;
public FooList
{
get => _fooList;
set
{
_fooList = value;
RaisePropertyChangedEvent(FooList);
}
}
public MainWindowModel(IFooListProvider provider)
{
FooList = provider.GetFooList();
}
}
And the View, with MainWindowModel as data context:
<TextBlock Text={Binding FooList.Name} />
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Everything works fine, I can delete and add new items, edit them and etc. All changes in View reflects automatically in ViewModel and Model via bindings and observable objects, and vice versa.
But now I need to add ToggleButton to data template of ItemsControl, which controls visibility of particular item in other part of window. I need IsChecked value in ViewModel, because control in other part of window is Windows Forms control and I can't bind IsChecked directly without ViewModel.
But I don't want to add new property (Visibility, for example) in model classes (Foo, FooCollection), because it is just an interface thing and it doesn't need to be saved or passed somewhere outside ViewModel.
So my question: what is the best way to add new property to Model collection in ViewModel?
I could create new collection of wrappers in ViewModel (some sort of class Wrapper { Foo item, bool Visibility }) and bind it to ItemsControl. But in this case I have to control adding, removing and editing manually and transfer all changes from List<Wrapper> to FooList.Items, so I don't like this solution. Is there any more simple way to achieve this?
Edition to clarify the question. Now I have:
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<ToggleButton IsChecked={Binding ????????????} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have no field in class to bind IsChecked and I don't want to add it to class, because it's only interface thing and not data model field. How can I, for example, create another collection of bools and bind it to this ItemControl alongside with FooList.Items?
The best place to add the property is of course in the Foo class.
Creating another collection of some other type, add an object per Foo object in the current collection to this one, and then bind to some property of this new object seems like a really bad solution compared to simply adding a property to your current class.
Foo is not an "interface thing", or at least it shouldn't be. It is view model that is supposed to contain properties that the view binds to. There is nothing wrong with adding an IsChecked property to it. This certainly sounds like the best solution in your case.
I'm not sure if I understand why you would need to add a property in the model.
Can't you just use the command property or add an EventTrigger to your toggle button?
(See Sega and Arseny answer for both examples Executing a command on Checkbox.Checked or Unchecked )
This way, when you check the toggleButton, there is a method in your viewModel which enable or disable the visibility property of your Winform control.
To change the visibility of your control from a command in your viewModel, you could use the messenger functionnality of MVVM LIGHT
MVVM Light Messenger - Sending and Registering Objects
The ViewModel sends a message to you're Windows Forms and this one handles the visibility of your control.

WPF: binding elements in main window and user control

I am new to WPF and MVVM and not sure how to solve this (probbaly simple) problem. I have a MainWindow with two regions: RibbonView and below it a TabControl (Telerik). I have big buttons in the ribbon control which are always enabled. For example, I have a button "Tenants" and when I click on this button I create a new tab item and place my TenantControl containing a GridView control (also from Telerik). Its content is automatically loaded.
Now I want to get the following to work: additionally I have some small buttons (Add, Delete, Clone etc) (Ribbon control) that are disabled by default and I want to enable these buttons when I select an item in a grid. Is the using of routed commands the proper way to solve the problem? How to enable a button in a main window if I select an item in a grid in own user control? And yes in a main window I have a reference to the user control. No need for code just a concept is needed - as I said before I am completelly new to WPF/MVVM.
Thanks for any help and best regards,
Erno
In my WPF Applications, I often use RibbonBars as well, but instead of creating different TabItems for the different views, I display them all in one place by using a ContentControl and then just changing the Content value to different view models which are then rendered as the relevant views:
In Mainview:
<ContentControl Content="{Binding ViewModel}" />
...
In MainViewModel:
ViewModel = new SomethingViewModel();
...
In App.xaml:
<DataTemplate DataType="{x:Type ViewModels:SomethingViewModel}">
<Views:SomethingView />
</DataTemplate>
In my MainViewModel class, I have a helper method:
private bool IsViewModelOfType<T>()
{
return ViewModel != null && ViewModel.GetType() == typeof(T);
}
This method is used in the ICommand.CanExecute property to enable and disable Commands dependant on the loaded view model:
public ICommand CopyTrackList
{
get { return new ActionCommand(action => ClipboardManager.SetClipboardText(
((ReleaseLabelCopyViewModel)ViewModel).TrackList), canExecute =>
IsViewModelOfType<ReleaseLabelCopyViewModel>() &&
((ReleaseLabelCopyViewModel)ViewModel).SomePropertyInChildViewModel == true); }
}

Selecting user control based on type of DataContext

I'm trying to build a set of typical CRUD maintenance forms in WPF - that are going to be pretty much the same except that they work on different database records.
Rather than creating a new window class for each, I'm trying to use a single window class that instantiated with a different ViewModel class for each database table, and for which I have a different UserControl defined for each ViewModel.
So, if I instantiate the window with its DataContext set to an instance of Record1ViewModel, I want to display it in the window using a Record1UserControl, if it's set to an instance of Record2ViewModel, I want to display it using a Record2UserControl.
I've verified that both user controls work fine, by defining them each directly in the window's XAML. But I've not figured out how to select one or the other, based on the type of the ViewModel.
This is not working:
<myWindow.Resources>
<DataTemplate x:Key="{x:Type ViewModels:Record1ViewModel}">
<MaintenanceControls:Record1 />
</DataTemplate>
<DataTemplate x:Key="{x:Type ViewModels:Record2ViewModel}">
<MaintenanceControls:Record1 />
</DataTemplate>
</myWindow.Resources>
<ContentPresenter Content="{Binding}" />
What I get, in the ContentPresenter, is the name of the type. The DataTemplates are not used.
Any ideas?
You can use the DataTemplateSelector to dynamically select a DataTemplate at run time something along the lines of
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is Task)
{
Task taskitem = item as Task;
if (taskitem.Priority == 1)
return
element.FindResource("importantTaskTemplate") as DataTemplate;
else
return
element.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}

How to pass the selectedItem of a listbox to the View Model

This is a running question that I have updated to hopefully be a little more clear.
In short what I am trying to accomplish is pass a property from a listbox selected item to the viewmodel so that this property can be used within a new query. In the code below the Listbox inherits databinding from the parent object. The listbox contains data templates (user controls) used to render out detailed results.
The issue I am having is that within the user control I have an expander which when clicked calls a command from the ViewModel. From what I can see the Listbox object is loosing it's data context so in order for the command to be called when the expander is expanded I have to explicitly set the datacontext of the expander. Doing this seems to instantiate a new view model which resets my bound property (SelectedItemsID) to null.
Is there a way to pass the selected item from the view to the viewmodel and prevent the value from being reset to null when a button calls a command from within the templated listbox item?
I realize that both Prism and MVVMLite have workarounds for this but I am not familiar with either framework so I don't know the level of complexity in cutting either of these into my project.
Can this be accomplished outside of Prism or MVVMLite?
original post follows:
Within my project I have a listbox usercontrol which contains a custom data template.
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding SearchResults[0].Results,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="ResultListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
<!-- CFS Template -->
<dts:TypeTemplateSelector.CFSTemplate>
<DataTemplate>
<qr:srchCFS />
</DataTemplate>
</dts:TypeTemplateSelector.CFSTemplate>
<!-- Person Template -->
<dts:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:srchPerson />
</DataTemplate>
</dts:TypeTemplateSelector.PersonTemplate>
<!-- removed for brevity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectionChanged calls the following method from the code behind
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
}
Within the ViewModel I have the following property
public string SelectedItemID
{
get
{
return this._SelectedItemID;
}
set
{
if (this._SelectedItemID == value)
return;
this._SelectedItemID = value;
}
}
the listbox template contains a custom layout with an expander control. The expander control is used to display more details related to the selected item. These details (collection) are created by making a new call to my proxy. To do this with an expander control I used the Expressions InvokeCommandAction
<toolkit:Expander Height="auto"
Margin="0,0,-2,0"
Foreground="#FFFFC21C"
Header="View Details"
IsExpanded="False"
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
Style="{StaticResource DetailExpander}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Within the ViewModel the delegate command GetCFSResultCommandExecute which is called is fairly straight forward
private void GetCfsResultCommandExecute(object parameter)
{
long IdResult;
if (long.TryParse(SelectedItemID, out IdResult))
{
this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}
The issue I am experiencing is when selecting a listbox Item the selectionchanged event fires and the property SelectedItemID is updated with the correct id from the selected item. When I click on the expander the Command is fired but the property SelectedItemID is set to null. I have traced this with Silverlight-Spy and the events are consistent with what you would expect when the expander is clicked the listbox item loses focus, the expander (toggle) gets focus and there is a LeftMouseDownEvent but I cannot see anything happening that explains why the property is being set to null. I added the same code used in the selection changed event to a LostFocus event on the listboxt item and still received the same result.
I'd appreciate any help with understanding why the public property SelectedItemID is being set to null when the expander button which is part of the listbox control is being set to null. And of course I would REALLY appreciate any help in learning how prevent the property from being set to null and retaining the bound ID.
Update
I have attempted to remove the datacontext reference from the Expander as this was suggested to be the issue. From what I have since this is a data template item it "steps" out of the visual tree and looses reference to the datacontext of the control which is inherited from the parent object. If I attempt to set the datacontext in code for the control all bindings to properties are lost.
My next attempt was to set the datacontext for the expander control within the constructor as
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
This approach does not seem to work as InvokeCommandAction is never fired. This command only seems to trigger if data context is set on the expander.
thanks in advance
With this line you create a new SearchViewModelDataSource using its default constructor.
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
I guess this is why you find null because this is the default value for reference type.
You can resolve the issue by setting DataContext to the same instance used to the main controll (you can do it by code after all components are initialized).
Hope this help!
Edit
I don't think that binding may be lost after setting datacontext from code. I do it every time I need to share something between two or more model.
In relation to the code you've written :
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
Instead of using this.cfsExpander you can try to use the FindName method. Maybe this will return you the correct instance.
object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
Expander exp = item as Expander;
exp.DataContext = this._ViewModel;
}
Try if its work for you.
Of course, this._ViewModel has to expose a property of type ICommand named GetCfsResultCommand but I think this has been already done.
While this was a hacky approach I found an intermediate solution to get the listbox item value to the view model. I ended up using the selection changed event and passing the value directly to a public property wihtin my view model. Not the best approach but it resolved the issue short term
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
}
For this to fire I did have to also setup a property changed handler within the view to push the change to the VM. You can disregard the MySelectedValue line as it is secondary code I have in place for testing.
For those intereted the generic property changed handler
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

WPF A good way to make a view/edit control?

this is just a question to discuss - what is the best way to make a view/edit control in WPF? E.g. we have an entity object Person, that has some props (name, surname, address, phone etc.). One presentation of the control would be a read-only view. And the other would have the edit view for this 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 that the idea is clear. The two options I see right now
two grids with visibility switching and
a TabControl without its header panel
This is just a discussion question - not much trouble with it, yet I am just wondering if there are any other possibilities and elegant solutions to this.
Automatic Lock class
I wrote an "AutomaticLock" class that has an inherited attached "DoLock" property.
Setting the "DoLock" property to true re-templates all TextBoxes ComboBoxes, CheckBoxes, etc to be TextBlocks, non-editable CheckBoxes,etc. My code is set up so that other attached property can specify arbitrary template to use in locked ("view") mode, controls that should never automatically lock, etc.
Thus the same view can easily be used for both editing and viewing. Setting a single property changes it back and forth, and it is completely customizable because any control in the view can trigger on the "DoLock" property to change its appearance or behavior in arbitrary ways.
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 template on a control-by-control basis or it will allow you to use default templates defined either in the assembly containing the AutomaticLock class, in the assembly containing your custom control that the lock template applies to, in your local resources in your visual tree (including your application resources)
How to define AutomaticLock templates
Default templates for WPF standard controls are defined in the assembly containing the AutomaticLock class in a ResourceDictionary merged into Themes/Generic.xaml. For example, this template causes all TextBoxes 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>
Default templates for custom controls may be defined in the assembly containing the custom control in a ResourceDictionary mered into its Themes/Generic.xaml. The ComponentResourceKey is different in this case, 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 standard AutomaticLock template for a specific type, it can place an automatic lock template in its App.xaml, Window XAML, UserControl XAML, or in the ResourceDictionary of an individual control. In each case the ComponentResourceKey should be specified the same way as for custom controls:
x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}"
Lastly, an automatic lock template can be applied to a single control by setting its AutomaticLock.LockTemplate property.
How to use AutomaticLock in your UI
To use automatic locking:
Set AutomaticLock.Enabled="True" on any controls that should be automatically locked. This can be done in a style or directly on individual controls. It enables locking on the control but does not cause the control to actually lock.
When you want to lock, set AutomaticLock.DoLock="True" on your top-level control (Window, view, UserControl, etc) whenever you want the automatic locking to actually happen. You can bind AutomaticLock.DoLock to a checkbox or menu item, or you can control it in code.
Some tips on effectively switching between view and edit modes
This AutomaticLock class is great for switching betwen view and edit modes even if they are significantly different. I have several different techniques for constructing my views to accomodate layout differences while editing. Some of them are:
Make controls invisible during edit or view mode by setting either their Template or AutomaticLockTemplate to an empty template as the case may be. For example, suppose "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. In the top one set Template to the empty template so it doesn't show in Edit mode. In the bottom one set AutomaticLockTemplate to the empty template. Now only one will be visible at a time.
Use a ContentControl to replace borders, layout panels, buttons, etc surrounding content without affecting the content. The ContentControl's Template has the surrounding borders, panels, buttons, etc for edit mode. It also has an AutomaticLockTemplate that has the view mode version.
Use a Control to replace a rectangular section of your view. (By this I actually mean an object of class "Control", not a subclass therof.) Again, you put your edit mode version in the Template and your view mode version in the AutomaticLockTemplate.
Use a Grid with extra Auto-sized rows and columns. Use a trigger on the AutomaticLock.DoLock property to update the Row, Column, RowSpan, and ColumnSpan properties of the items within the Grid. For example you could move a panel containing an "Age" control to the top by changing its Grid.Row from 6 to 0.
Trigger on DoLock to apply a LayoutTranform or RenderTransform to your items, or to set other properties like Width and Height. This is useful if you want things to be bigger in edit mode, or if you want to make a TextBox wider and move the button beside it over against the edge.
Note that you can use option #3 (a Control object with separate templates for edit and view modes) for the entire view. This would be done if the edit and view modes were completely different. In this case AutomaticLock still gives you the convenience of being able to set the two templates manually. It would 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>
Generally it is easier to tweak a few little positions and things between the edit and view modes, and better for your user experience because the user will have consistent layout, but if you do need a complete replacement AutomaticLock gives you that power as well.
<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 a command rather, if you're familiar with.
I would create a single View with 2 different configoptions , f.e. 2 different constructors to make the the relevant field editable/readonly or visible/hidden
This way you don't write redundant XAML and you can configurate all fields over code behind or ViewModel when using MVVM
Sounds like a job for a DataTemplateSelector to me. If you would rather switch the individual controls in place I would do something similar to what Veer suggested.

Resources