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

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.

Related

Handling properties of custom controls in MVVM (WPF)

I have a custom control which will have properties that can be set which will affect the logic of how the control is handled. How should this be handled in MVVM?
Currently I'm stuck trying to pass a DependencyProperty to the ViewModel.
Example code:
CustomControl.xaml
<UserControl x:Name="Root" ...>
<UserControl.DataContext>
<local:CustomControlViewModel SetDefaultValue="{Binding ElementName=Root, Path=SetDefaultValue, Mode=TwoWay}"/>
</UserControl.DataContext>
...
</UserControl>
CustomControl.xaml.cs
...
public static readonly DependencyProperty SetDefaultValueProperty = DependencyProperty
.Register("SetDefaultValue",
typeof(bool),
typeof(CustomControl),
new FrameworkPropertyMetadata(false));
public string SetDefaultValue
{
get { return (string)GetValue(SetDefaultValueProperty ); }
set { SetValue(SetDefaultValueProperty , value); }
}
...
CustomControlViewModel.cs
...
private bool setDefaultValue;
public bool SetDefaultValue
{
get { return setDefaultValue; }
set
{
if (setDefaultValue!= value)
{
setDefaultValue= value;
OnPropertyChanged("SetDefaultValue"); // INotifyPropertyChanged
}
}
}
...
My goal with this property specifically is to be able to set a default value (getting the default value requires running business logic). So in another view I would use this control like this:
<local:CustomControl SetDefaultValue="True"/>
(Before I answer I want to point out that what you have here is actually a user control, not a custom control. That's not nit-picking on my part; A user control is something derived from the UserControl class and it typically has an associated XAML file. A custom control just derives from the Control class and has no associated XAML file. A custom control requires you set to a control template. Custom controls can be styled. User controls cannot.)
The thing about UserControl is that sometimes we create one assuming one specific DataContext, of one type and then we make all of its XAML bind to that object type. This is good for big, main pages of an application that are not meant to be re-used in too many places
But another approach -- that you have started to do here -- is to give our user controls their own dependency properties. So in this case, why not dispense with the need for this control to have any specific DataContext altogether? This is the first step to making user controls truly re-usable in many places.
Unless this control is huge, there's a good chance that When you are laying out its XAML, it can get everything that XAML needs to bind to in just a few properties. So why not make all those properties into dependency properties and make the control's XAML bind to itself?
Make the class be its own DataContext. Set that property on the root UI element of the control's layout and then every Binding should work well.
To illustrate, I've renamed your control class MyUserControl I've renamed your "SetDefaultValue" property to just be "BoolOption" Let's assume that all it needs to show is a checkbox, representing the bool value and a string label on the checkbox. We can do this with just two dependency properties. (In effect, this entire control is now just a pointless, glorified CheckBox control but please ignore that)
MyUserControl.xaml.cs
public static readonly DependencyProperty BoolOptionProperty =
DependencyProperty.Register(nameof(BoolOption),
typeof(bool),
typeof(MyUserControl),
new FrameworkPropertyMetadata(false));
public string BoolOption
{
get { return (string)GetValue(BoolOptionProperty ); }
set { SetValue(BoolOptionProperty , value); }
}
public static readonly DependencyProperty CheckBoxLabelProperty =
DependencyProperty.Register(nameof(CheckBoxLabel),
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(string.Empty));
public string CheckBoxLabel
{
get { return (string)GetValue(CheckBoxLabelProperty ); }
set { SetValue(CheckBoxLabelProperty , value); }
}
// Constructor. Here we set the control to be its own UI's DataContext
public MyUserControl()
{
InitializeComponent();
// Make us be the UI's DataContext. Note that I've set the
// x:Name property root Grid in XAML to be "RootUiElement"
RootUiElement.DataContext = this;
}
MyUserControl.xaml
<UserControl x:Class="MyCompany.MyApp.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyCompany.MyApp.Controls"
x:Name="Root"
d:DesignHeight="450"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:MyUserControl}}"
mc:Ignorable="d">
<Grid x:Name="RootUiElement">
<CheckBox IsChecked="{Binding BoolOption}"
Content="{Binding CheckBoxLabel"
/>
</Grid>
</UserControl>
Finally you could use the control anywhere you wanted, no matter what your current DataContext is, like this
<local:MyUserControl BoolOption="True" CheckBoxLabel="Is Option A enabled?"/>
<local:MyUserControl BoolOption="False" CheckBoxLabel="Is Option B?"/>
Or even bind it to some other DataContext where you're using it like this. Suppose my current DataContext is a view-model that has a boolean UserBoolOptionC property
<local:MyUserControl BoolOption="{Binding UseBoolOptionC}" "Is Option C enabled?"/>

Using base control in XAML, but loading a derived control

Here's a situation I am trying to solve:
I have a base UserControl from which I derive a number of other Controls that handle derivations of a base Object in a specific manner. (The purpose of this being to create a template for when additional derivations of the base control are needed later down the road.) What I would like to do is use the base control name as the tag in XAML, but when the control is actually rendered, show the derived control.
class BaseControl : UserControl { }
class DerivedControl1 : BaseControl { }
class DerivedControl2 : BaseControl { }
class BaseObject { }
class DerivedObject1 : BaseObject { // Requires DerivedControl1 to display }
class DerivedObject2 : BaseObject { // Requires DerivedControl2 to display }
class BaseContainerObject { }
class ContainerObject1 : BaseContainerObject
{
DerivedObject1 dObject0;
DerivedObject1 dObject1;
DerivedObject2 dObject2;
}
class ContainerObject2 : BaseContainerObject
{
DerivedObject2 dObject0;
DerivedObject2 dObject1;
DerivedObject1 dObject2;
}
window.xaml
<!-- Here is what I would like to do -->
<StackPanel>
<BaseControl Name="Object0" DependencyProperties="{Binding BaseContainerObject.dObject0}" />
<BaseControl Name="Object1" DependencyProperties="{Binding BaseContainerObject.dObject1}" />
<BaseControl Name="Object2" DependencyProperties="{Binding BaseContainerObject.dObject2}" />
</StackPanel>
I've played around with styles and data triggers to detect the specific type of ContainerObject, but I haven't found the right pattern to encapsulate a ContainerObject in a single template-able "package" yet.
I could dynamically add the controls from the code-behind, but I haven't had any luck with that so far. (The top-level of the control appears on VisualTree, but no children appear on the tree and none are rendered.)
Any thoughts?
EDIT:
I can't post a screenshot at the moment, but perhaps I can add a little more detail.
I have a data object (the DataContext for the window) that has up to nine attributes (the DerivedObjects) that the user will need to edit in my window. The meaning of those nine attributes, and, in turn, how they should be expressed in UI controls, changes based on the attributes of a second data object the user selects in a previous step. (That is the ContainerObject. The other data object is not referenced in the above code, although it contains a reference to the second data object.)
Those attributes can be expressed in four different ways: a text box (for continuous values), a combobox (for discrete values), a checkbox (for boolean values) and radio buttons (for a choice between two values).
I have created UserControls that package those controls in a horizontal Grid with 1) a label for the value's definition, 2) the value's units (if applicable) and, if applicable, 3) a checkbox to view the value in an alternate format (i.e. viewing a decimal number in hex). (Those are the DerivedControls that inherit from an XAML-less BaseControl that stores common properties and functions.) To maintain proper column alignment over the entire collection, I specify four column widths in a Style at the Window level and use a Converter to handle alignment for attributes that do not require the units and/or the alt-display checkbox.
When the user selects the second object in the previous step, the nine rows of the collection control should look to the second data object reference of the DataContext object to select the proper template and populate the other labels. Because I will need to use this collection in other programs, I am creating it in a separate assembly.
I know I am pigeon-holing myself in some fashion on this. I am trying to do this with as little code as possible, but I can't think of the right code pattern to use here. Every component is working fine, but I can't seem to get it all to come together in a simple way so I can work out the last few little bugs.
Thanks. I am just learning WPF, and I really like. I'm just at the point of trying to get my head wrapped around some of the finer details.
Here is a pretty good example from wpftutorial.net of what it sounds like you need. To summarize, you can use a DataTemplate to define how an object is displayed within a repeating control such as a ListBox, ComboBox or ListView. You can override the styles of those to make them appear as you want, or sometimes it's just easier to use ItemsControl (the control they inherit from) directly. They have a property named ItemsPanel that will allow you to specify a StackPanel as the ItemsPanelTemplate so you get the same desired layout of the objects as you showed above.
Setting how an object is dispalyed via a DataTemplate is great, but you want to dynamically change that template based on the type of the bound object if I understand correctly. This can be accomplished by creating a DataTemplateSelector.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultDataTemplate { get; set; }
public DataTemplate DerivedObject1Template { get; set; }
public DataTemplate DerivedObject2Template { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DataTemplate selectedTemplate = DefaultDataTemplate;
if (item is DerivedObject1)
{
selectedTemplate = DerivedObject1Template
}
else if (item is DerivedObject2)
{
selectedTemplate = DerivedObject2Template;
}
return selectedTemplate;
}
}
And then your XAML can use the template selector on the repeating control:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:..."
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Window.Resources>
<!-- Default DataTemplate -->
<DataTemplate x:Key="DefaultDataTemplateResource">
...
</DataTemplate>
<!-- DataTemplate for Booleans -->
<DataTemplate x:Key="DerivedObject1TemplateResource">
<local:DerivedControl1 .../>
</DataTemplate>
<!-- DataTemplate for Enums -->
<DataTemplate x:Key="DerivedObject2TemplateResource">
<local:DerivedControl2 .../>
</DataTemplate>
<!-- DataTemplate Selector -->
<local:PropertyDataTemplateSelector x:Key="myCustomTemplateSelector"
DefaultnDataTemplate="{StaticResource DefaultDataTemplateResource}"
DerivedObject1Template = "{StaticResource DerivedObject1TemplateResource}"
DerivedObject2Template = "{StaticResource DerivedObject2TemplateResource}"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource myCustomTemplateSelector}"/>
</Grid>
</Window>
Hopefully that will get you started!

WPF Binding to UserControl Custom DependencyProperty

I have a custom UserControl called SongDescription:
<UserControl x:Class="DPTestAp.SongDescription" ...>
<Grid x:Name="LayoutRoot">
<DockPanel Height="50">
<TextBlock x:Name="title" Text="{Binding name}" Width="100" Height="30"/>
<TextBox x:Name="lyrics"/>
</DockPanel>
</Grid>
</UserControl>
I added DependencyProperty to it:
public partial class SongDescription : UserControl
{
public static readonly DependencyProperty SongProperty = DependencyProperty.Register("Song", typeof(Song), typeof(SongDescription));
public Song Song
{
get
{
return (Song)GetValue(SongProperty);
}
set
{
SetValue(SongProperty, value);
updateLyrics()
}
}
private void updateLyrics()
{
lyrics.Text = Song.lyrics;
}
public SongDescription()
{
InitializeComponent();
}
}
The question is: how to bind something to this SongProperty?
I use SongDescription in my main window like this:
<local:SongDescription x:Name="songDescription" Song="{Binding DataContext}"/>
I cannot make my TextBox lyrics show lyrics. In main window I tried to set DataContext to songDescription, like this:
songDescription.DataContext = new Song() { name="Home", lyrics="Hold on, to me as we go" };
or to window itself like this:
DataContext = new Song() { name="Home", lyrics="Hold on, to me as we go" };
I even tried to make Song a resource and bind it to SongProperty like this:
<Window.Resources>
<local:Song x:Key="res" name="Home" lyrics="Hold on, to me as we go"/>
</Window.Resources>
<Grid>
<local:SongDescription x:Name="songDescription" Song="{StaticResource res}"/>
</Grid>
Nothing helped. TextBlock title binds song name fine. But I can't make updateLyrics() method be called. (In real life this method is more complicated, so I can't use Binding like with name).
Thank you!
Yup, so that's a gotcha with dependency properties. You never ever put validation code inside of the accessor methods (get/set) because dependency properties are stored by WPF in a table that it itself manages. This is why you have to register dependency properties, it essentially creates entries on this table for storing the values associated with each property, and when you use 'GetValue' / 'SetValue' you are updating the entries on this table (which by the way relates to how WPF is able to manage data bindings in general).
The upshot of this though is that WPF can (and will) completely bypass your property accessors because it has direct access to the real data. Why should it use your accessors if it can just go to the data directly. Instead you need to implement a 'PropertyChanged' callback function or some WPF sanctioned method of doing validation, but never ever do it in your accessors.
See:
http://msdn.microsoft.com/en-us/library/ms752914.aspx
In addition to sircodesalot's answer, you are not bound on your lyrics textbox. Also, since the song your bound to is a class, you will need to specify the paths fully for the properties you want to show in the boxes such as "Path=Song.Lyrics".
Another thing to consider is that with dependency properties; your mode will be oneway by default so making the text field editable would be moot really unless you change it.
Third, if you're using MVVM you only need your main window context to be set to the view model and have a matching Song property to bind against.

Chaining DependencyProperties

Let's say I have a custom control which wraps another control (for example MyCustomButton). I expose a property Content, which wraps the inner control:
public object Content
{
get { return innerControl.Content; }
set { innerControl.Content = value; }
}
In order for a consumer to bind to this property, I need to define a DependencyProperty for it:
public static DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof (object), typeof (MyCustomButton));
but now I need my property definition to use GetValue/SetValue:
public object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
so I'm not wrapping the value of the inner control anymore.
I can define PropertyMetadata to handle the PropertyChanged event of the DependencyProperty, but then I need a bunch of plumbing code to keep the values in sync and prevent infinite loopbacks on changed.
UPDATE: I can't just derive from Button because my UserControl has various other concerns.
Is there a better way to do this?
Well, depending on the particulars of why you're wrapping a button with a user control, you could define a custom control that inherits from button. Then, instead of wrapping the button and exposing the wrapped methods and properties that you want, you can simply override methods and properties whose behavior you want to define the custom control. This way, you'll get all of the functionality of button without the need to reinvent the wheel.
Here's a google link that walks you through it (one of the first that I found - there are plenty): http://knol.google.com/k/creating-custom-controls-with-c-net#
If the user control has other concerns, this may not be an option for you, but I'm offering this answer because the only purpose that you've mentioned for it is wrapping the button. I'd personally favor creating a custom control and inheriting rather than a user control and wrapping if the control in question is simply meant to be a more specific kind of wrapped/inherited control (i.e. button in your case).
Edit: In light of updated question...
You could do something along these lines. Here is the XAML of the client of your user control:
<Grid>
<local:MyControl ButtonContent="Click Me!"/>
</Grid>
</Window>
Here is the XAML for the user control itself:
<UserControl x:Class="GuiScratch.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:GuiScratch"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<ContentControl Content="Asdf"/>
<Button Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyControl}},Path=ButtonContent}"/>
</StackPanel>
</Grid>
</UserControl>
And, here is the code behind:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty ButtonContentProperty =
DependencyProperty.Register("ButtonContent", typeof(object), typeof(MyControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public object ButtonContent
{
get { return (object)GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
public MyControl()
{
InitializeComponent();
}
}
So, you don't need to handle the binding at all through code. Your client XAML binds to your dependency property, as does the XAML of the user control itself. In this fashion, they share the dependency property setting. I ran this in my little scratchpad, and the result is (at least my understanding of) what you're looking for. The main window displays the user control as a stack panel with the text "Asdf" and then a button with the text "Click Me!"

How to create a Silverlight control that has two content areas

I want to create a Silverlight 2 control that has two content areas. A Title and a MainContent. So the control would be:
<StackPanel>
<TextBlock Text=" CONTENT1 "/>
<Content with CONTENT2 "/>
</StackPanel>
When I use the control I should just be able to use:
<MyControl Text="somecontent">main content </MyControl>
How can I create such a control?
You can do that easily with the ContentProperty attribute.
Then you can define your code behind as:
[ContentProperty("Child")]
public partial class MyControl: UserControl
{
public static readonly DependencyProperty ChildProperty = DependencyProperty.Register("Child", typeof(UIElement), typeof(MyControl), null);
public UIElement Child
{
get { return (UIElement)this.GetValue(ChildProperty); }
set
{
this.SetValue(ChildProperty, value);
this.content.Content = value;
}
}
What that will do is any default content within your tags (<MyControl Text="somecontent">main content </MyControl>) - will be set as the Child property on your class. Then once it's been set you can assign it to any control you like.
Edit:
You can have as many contents as you like, but you can only have 1 auto-content (which is designated via the ContentProperty attribute).
If you want two you could do:
<MyControl>
<MyControl.Content1>Hello World</MyControl.Content1>
<MyControl.Content2>Goodbye World</MyControl.Content2>
</MyControl>
All you have to do is make sure you have the matching dependency properties in your code. Then when the property is set, just assign it to a parent content control in your XAML.
What you wanted is a Silverlight version of the WPF HeaderedContentControl
You can find a try here. http://leeontech.wordpress.com/2008/03/11/headeredcontentcontrol-sample/

Resources