wpf custom button best approach - wpf

I want to create a custom Button inside WPF. Of course, the button will be an UserControl and it will contain many visual elements (like stroke, highlight, shadow, glow, images etc.).
The problem is that if I use DependencyProperties and bind them in XAML I won't be able to see the result at DesignTime (I've tried to implement IsInDesignMode method but for a certain reason that I can't understand my VS just crashes when I use this method on UserControls, otherwise it works just fine) and this is definitely not good.
So I am thinking about not using XAML at all and do all my work in the code behind.
What do you guys think?

Like you, when I was getting started and wanted to understand how / what was going on and working with templates, it took a lot of trial and error. Hopefully my research and some step-by-step components can help you customize to your liking and KNOWING where things are coming from.
First, when trying to understand how a new "template style" will work, I created a simple stand-alone WPF app ("AMS") for my Any Manipulating Styles. This way, I don't have to wait forever to see what something will look like during trial / error with the rest of my primary project and themes.
From that, I created a new WPF Window called "TestingStyles". Save/Compile, run, no problem.
Now, in the "VIEW CODE" of the TestingStyles window, I have put whatever I am playing with for a custom class... To help show the step-by-step, I've created the following:
namespace AMS
{
/// <summary>
/// Interaction logic for TestingStyles.xaml
/// </summary>
public partial class TestingStyles : Window
{
public TestingStyles()
{
InitializeComponent();
}
}
// Enumerator for a custom property sample...
public enum HowToShowStatus
{
ShowNothing,
ShowImage1
}
public class YourCustomButtonClass : Button
{
public YourCustomButtonClass()
{
// auto-register any "click" will call our own custom "click" handler
// which will change the status... This could also be done to simplify
// by only changing visibility, but shows how you could apply via other
// custom properties too.
Click += MyCustomClick;
}
protected void MyCustomClick(object sender, RoutedEventArgs e)
{
if( this.ShowStatus == HowToShowStatus.ShowImage1 )
this.ShowStatus = HowToShowStatus.ShowNothing;
else
this.ShowStatus = HowToShowStatus.ShowImage1;
}
public static readonly DependencyProperty ShowStatusProperty =
DependencyProperty.Register("ShowStatus", typeof(HowToShowStatus),
typeof(YourCustomButtonClass), new UIPropertyMetadata(HowToShowStatus.ShowNothing));
public HowToShowStatus ShowStatus
{
get { return (HowToShowStatus)GetValue(ShowStatusProperty); }
set { SetValue(ShowStatusProperty, value); }
}
}
}
As you can see, the custom "Button" class, I have at the bottom outside the default TestingStyles : Window declaration... so its all in the same "Project".
In this XAML sample, I make reference to a "TaskComplete.png" graphic file (which should just for sample purposes, add directly to the project... Even if a simple smiley face for sample purposes).
So, create such a simple .png file... even by using Microsoft Paint and drawing a circle with eyes and smile. Save into the project at the root (get into pathing stuff later, get it working first).
Save and recompile the project, so the project knows publicly what the new "class" (button) is when you start to define the XAML template.
Now, back to the TestingStyles designer and get it into split screen so you can see both the designer and the XAML markup... and Just replace with the following...
<Window x:Class="AMS.TestingStyles"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:AMS"
Title="TestingStyles" Height="300" Width="300" >
<Window.Resources>
<!-- Build a "Style" based on an anticpated target control type of YourCustomButtonClass.
per the "my:" reference, the "my" is an "alias" to the xmlsn:my in the declaration above,
so the XAML knows which library to find such control. In this case, I've included within
the actual forms's 'View Code' as a class at the bottom.
As soon as you assign an "x:Key" reference, its like its telling XAML to make this a PRIVATE
style so you don't reference it explicitly (yet)
-->
<Style TargetType="my:YourCustomButtonClass" x:Key="keyYourCustomButtonClass">
<!-- put whatever normal "settings" you want for your common look / feel, color -->
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="0,0,1,1"/>
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="30" />
<!-- Now, for the template of the button. Things can get really crazy here
as you are now defining what you want the "button" to look like, borders,
content, etc. In this case, I have two borders to give the raise/sunken effect
of a button and it has its own colors -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button" >
<!-- The x:Name references used during triggers to know what it is "applying" changes to -->
<Border x:Name="BorderTopLeft"
BorderBrush="Gainsboro"
BorderThickness="0,0,1.5,1.5">
<Border x:Name="BorderBottomRight"
BorderBrush="Gray"
BorderThickness="1.5,1.5,0,0">
<!-- Now, what control type do you want the button to have...
Ex: You could use a grid (as I have here), stack panels, etc -->
<Grid Background="LightBlue" >
<!-- I'm defining as two columns wide, one row tall.
First column fixed width 20 pixels example for an image -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20px" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<!-- Now, create the controls I want available within my "template".
when assigned with "x:Name", thats like a property withing the template
that triggers can associate and update to. -->
<Image x:Name="btnImage"
Grid.Row="0" Grid.Column="0"
Stretch="None"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Source="TaskComplete.png"
Visibility="Visible" />
<!-- and also have the text for the button to show the user -->
<TextBlock x:Name="txtNewBtn"
Grid.Row="0" Grid.Column="1"
Padding="5"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{TemplateBinding Content}" />
<!-- The "{TemplateBinding Content}" means to set the text based on
the "CONTENT" property of the original button and not use a fixed value -->
</Grid>
</Border>
</Border>
<!-- Now, some triggers for the button itself... some can be property based, others data-based -->
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<!-- What properties do we want to change when user CLICKS
on the button, give the "EFFECT" of click down/up by
changing the "Margin" and border thicknesses... -->
<Setter Property="Margin" Value="1,1,0,0"/>
<!-- Notice the "TargetName" below referring to the x:Name I've applied in template above
so when the user clicks on the button, it changes the border thickness properties of
each to give the effect of a normal button clicking. I'm widening one border, shrinking other -->
<Setter TargetName="BorderTopLeft" Property="BorderThickness" Value="2.5,2.5,0,0"/>
<Setter TargetName="BorderBottomRight" Property="BorderThickness" Value="0,0,.5,.5"/>
</Trigger>
<!-- Here, I have a custome property on the class for "ShowStatus". The binding is to itself
regardless of how many instances of this type of "button" are on a given form
First trigger happens when the value is changed to "ShowNothing", but can also change
when set to "ShowImage1" or other as you may need applicable
-->
<DataTrigger Binding="{Binding Path=ShowStatus, RelativeSource={RelativeSource Self}}" Value="ShowNothing">
<Setter TargetName="btnImage" Property="Visibility" Value="Hidden"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=ShowStatus, RelativeSource={RelativeSource Self}}" Value="ShowImage1">
<Setter TargetName="btnImage" Property="Visibility" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- NOW, we can expose any instance of "YourCustomButtonClass" button to use the style based on definition above
any instance of such YourCustomButtonClass will automatically reflect this style / look -->
<Style TargetType="my:YourCustomButtonClass" BasedOn="{StaticResource keyYourCustomButtonClass}" />
</Window.Resources>
<Grid>
<my:YourCustomButtonClass Content="Button" VerticalAlignment="Top" ShowStatus="ShowImage1" />
</Grid>
</Window>
This should give you a great jump-start to defining your own templates and how the elements start to tie together. Once this sample is running, as you change any colors, margins, padding, etc to the template, you'll immediately see the visual impact that component has on the control.
Have fun and don't bang your head too much against the wall...
BTW, once this is working, then you can take the style element stuff within the
<Window.Resources>
</Window.Resources>
and put it into a Windows Resource Dictionary to make it global to your project instead of just this test form.

Related

WPF MVVM Light - About views communicating

I am currently working on a project for work. I am seeking an outside design opinion, as well as some general information on the issue I am faced with.
We have a MainWindow.xaml file that is located in the root directory of the project. In this main window is some design and logic for some collapsing stack panels, ribbon toolbar, etc.
So far the idea is to include a different in each stack panel to help make the code neat. The views are located in a 'Views' folder. So just to be clear, the MainWindow.xaml and other views ARE NOT in the same directory. This is open to change, if necessary.
So here is my question/issue: We have a Window ('A'), a main panel with a collapsable stack panel with some information ('B') that is contained in window 'A'. Then there is another stack panel to manage the contents in 'B', (collapse/visisble) ('C').
'A' contains a toggle button to show/collapse 'B'.
'B' contains a button to show/collapse 'C'.
'C' contains a button to show/collapse itself, 'C'.
'C' should have its logic all contain within a view, so the MainWindow ('A') should have a simple tag:
<StackPanel Style="{StaticResource FrameGradient}" Tag="{Binding ElementName=ToggleButton}">
<view:Content></view:Content>
</StackPanel>
Currently, the bindings for toggling the buttons within 'A' are in the styling. The In this case FrameGradient has triggers like so:
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
//Setter properties
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
Is it possible to, within the 'Content' View to TOGGLE the panel, 'C', which is NOT within the view? I feel like I am missing a core idea of XAML here. I found a 'cheap' work around which is to place the 'Close' button from the Content View outside of the tags, but then that leads to styling issues and I feel like I shouldn't have to do something silly like that. Again, the idea is that the toggle button for Stack Panel 'C' is contained within another view and I want to be able to toggle it from another view.
I apologize if I am not clear enough, I will provide whoever asks with more information if required here.
UPDATE
I have some time to actually add the code I am using so that this might make more sense.
MainWindow.xaml - Logic for Filter panel (Located in root)
<StackPanel Grid.Row="1" Grid.Column="4" Visibility="Collapsed" Style="{StaticResource FrameGradient}">
<Grid x:Name="FilterContentGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<view:Filters></view:Filters>
</Grid>
</StackPanel>
Filters.xaml - Logic for Filters view (Located in /Views)
The button within the file that needs to Collapse the above StackPanel.
<Button x:Name="FilterManagementCloseButton" Content="CLOSE"></Button>
Theme.Xaml - Logic for all styling (Located in root, along with MainWindow.xaml and App.xaml)
Button Styling
<Style x:Key="FilterManagementCloseButton" TargetType="Button">
<Setter Property="Padding" Value="10,5,20,3" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource AncestorType={x:Type Local:MainWindow}}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
And finally, the FrameGradient Styling also located in Theme.xaml
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
SO, I hope this makes things more clear. I want the CLOSE button within Filters.xaml to COLLAPSE the stackpanel that is located in MainWindow. I realize this code is a mess at the moment.
Is it possible to, within the 'Content' View to TOGGLE the panel, 'C',
which is NOT within the view?
Create a shared VM which each other VM will have a property for which it can access. This VM can be loaded with during initialization of the other VMs. To allow for changes to happen put INotifyProperty(ies) on the shared VM which will then flag the desired logic across all views. Finally bind the target control(s) to your datacontext as normal except sub path into the shared VM target's property.
Hence when one view toggles (two way binding) a shared property it is reflected on the view of the target panel.
Update Example
The idea here is that one creates a viewmodel for the AppPage. That VM will hold generic flags which are shared across all viewmodels. Each subsequently created ViewModel will have a reference to the AppPage's viewmodel.
The example below is a mainpage where the AppVM contains a flag which informs the mainpage whether a login is in process. If it is and that value is true then a bound button on the mainpage will be enabled.
Subsequently the mainpage can override the appvm and put a new value within that flag by a bounded checkbox that can in-directly change whether the button is enabled; thus changing the flag for all other VMs in the process.
Here is the Mainpage VM, for this example I simply create the AppVM, but it could be passed in, or gotten from a static reference elsewhere. NOTE also how I don't care when AVs (appVM) property changes; it is not required for this example (we are not binding anything to AppVM, just its properties which need to be monitored).
public class MainVM : INotifyPropertyChanged
{
public AppVM AV { get; set; }
public MainVM()
{
AV = new AppVM() { LoginInProcess = true };
}
}
Here is the AppVm
public class AppVM : INotifyPropertyChanged
{
private bool _LoginInProcess;
public bool LoginInProcess
{
get { return _LoginInProcess; }
set { _LoginInProcess = value; OnPropertyChanged(); }
}
}
Here is MainPage's Xaml where the datacontext has been set to an instance of MainVM:
<StackPanel Orientation="Vertical">
<CheckBox Content="Override"
IsChecked="{Binding AV.LoginInProcess, Mode=TwoWay}"/>
<Button Content="Login"
IsEnabled="{Binding AV.LoginInProcess}"
Width="75" />
</StackPanel>
I base the MVVM off of my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding which explains the other missing items of this example such as the mainpage's datacontext loading.
You can use RelativeSource Bindings to bind from child views to properties in parent view models. Let's say you have a ToggleButton in MainWindow.xaml that is data bound to a property named IsChecked, which is declared in the object that is data bound to the MainWindow DataContext property. You could data bind to that same property from any child view with a RelativeSource Binding, like this:
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
<Setter Property="StackPanel.Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.IsChecked, RelativeSource={
RelativeSource AncestorType={x:Type Local:MainWindow}}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<!-- Note that there is no need for two Triggers here -->
<!-- One Setter and one Trigger is enough -->
</Style.Triggers>
</Style>

WPF shared property

I have a question which I guess it's some basic knowledge which I missing in WPF.
I set default width (generix.XML) to Textbox with some Minim width for the textbox
<Style TargetType="{x:Type TextBox}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="MinWidth" Value="50"/>
</Style>
I have two deferent controls which holds text box. Both Textboxes has same width..
I which to add some property to one of the controls which will declare the width of the textbox, and will override its width declaration, in a way that the textbox will 'find' to this property.
here is some drawing describes my requirement:
Update:
I just figure out that I didn't described one more importing thing.
I Have some DataTemplate which uses the textbox. As I wrote above, I have two controls which have the same DataType (MyData) I also created DateTemplate to display MyData. I would like that each control will display the textbox (from the datatemple) with different width.
update 2:
here is some more code
1- The dataTemplate to my data where is using textbox
<DataTemplate DataType="{x:Type ml:MyData}">
<Border BorderBrush="Transparent" ClipToBounds="True" Style="{StaticResource errorBorder}">
<TextBox Text="{Binding MyText}"/>
</Border>
</DataTemplate>
2- the way I used the datatemplate which uses the Textbox.
<ContentPresenter Grid.Column="1" Margin="10,1,10,1" HorizontalAlignment="Left" Content="{Binding}" />
This contentPresentor is been displayed in two diffrent controls. and as I wrote before, I would like that each control will display the textbox in diferent width
It's look like I miss some basic knloage (attached proerty? logic/visual tree?).
Thanks, Leon
Good question, the main idea in DataTemplate is that you have specific graphical representation for some data. You can read more about it in MSDN.
If you want to customize your TextBox, and have it different properties inside different UserControls, you might want to use ControlTemplate.
The thing is that if you want to control properties of specific control (in this case TextBox with some border) you should use ControlTemplate.
Your XAML should look something like:
<ControlTemplate TargetType="{x:Type TextBox}">
<--! define the ControlTemplate here with some Width property-->
<ControlTemplate>
and the Control which use it will have TextBox (as you defined it, with Border):
<TextBox Grid.Column="1" Margin="10,1,10,1" HorizontalAlignment="Left" Content="{Binding}" Width="50"/>

Style to choose suitable foreground according to background color

I have a WPF application which ships with a set of default styles for Label, TextBox etc. defined by item types (no explicit keys defined).
Within the application there are two main containers used, one with dark background and one with light background, such that sometimes it's right to use black as the foreground color for a Label and sometimes its dramatically wrong. On the other hand, editors are always styled rather traditionally with light background and dark foreground, so I cannot just set the foreground for all child elements to the inverse.
Is there an elegant way to make my labels (and maybe TextBlocks as well) to decide about their foreground color dependent on 'their' background? I only want to switch between two colors, thus no auto-contrast-maximization needed, only some threshold to avoid white font on white ground.
I also don't want to define two sets of default styles, I strongly search for some way to make my single Label-Default-Style be appropriate for both background variants.
Is it possible (and feasible without too much performance hit) to add a trigger/binding to the style, which evaluates the current background color?
Alternatively, I would be interested in best practices how to cleanly set background-colors for certain FrameworkElements, especially containers/panels, without running into the problems described above.
Here is what I tried (simplified of course):
<UniformGrid>
<UniformGrid.Resources>
<!-- SimpleStyles: Label -->
<Style x:Key="{x:Type Label}" TargetType="{x:Type Label}">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<Border>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Background" Value="Black">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#888888"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UniformGrid.Resources>
<Label x:Name="bgSetExplicitly" Background="Black">abc
</Label>
<Border Background="Black">
<Label x:Name="bgInheritedFromParent" >abc
</Label>
</Border>
<Label>abc
</Label>
<Label>abc
</Label>
You can see that the label's background is chosen nicely, if the Label has an explicit background set (x:Name=bgSetExplicitly), but if the background is 'inherited' from the parent in the VisualTree (x:Name="bgInheritedFromParent"), it's not.
I would love to have that working that the style can evaluate the "effective background" (no matter where it comes from) and choose an appropriate foreeground-brush for this background.
This question seems to imply a deeper issue. I would guess that you haven't consolidated the management of foreground colors, and so you have an application that has code or styles that set foreground colors all over the place. And now you're dealing with one of the implications of that.
I'd face the deeper issue and fix that. I don't really understand why you're resistant to the idea of creating default styles, but assuming for the moment that you have a good reason not to do this (other than "I should have created default styles at some point, but now that I haven't and my application has gotten big, it's just too hard," in which case, I have a different suggestion), how about creating global resources?
Create objects in the application's resource dictionary for the controls' foreground and background colors, and fix the XAML so that instead of referencing brushes directly, it uses the DynamicResource markup extension to get them from the resource dictionary. In code, instead of setting the Foreground property on the controls to a brush directly, use GetResource to get the brush. And set the background the same way.
Once you've done this, if you want to change foreground/background colors globally in your application, you just change the resources.
This is basically how you start making a WPF application skinnable, which seems to be the road you're going down.
The easiest thing to do is bind the foreground color of your TextBoxes, etc. to the background of their containing element, and use a value converter to make the change. This is especially easy if you are only ever using two colors.
As an example....
public class ColorSwapper : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value == null) { return DependencyProperty.UnsetValue; }
Color srcColor = (value as SolidColorBrush).Color;
if (srcColor == Colors.Black)
{
return new SolidColorBrush(Colors.White);
}
else
{
return new SolidColorBrush(Colors.Black);
}
}
...
And some example XAML....
<Grid Grid.Column="1" Name="RightGrid" Background="White">
<TextBlock Text="Hello Nurse!" Foreground="{Binding ElementName=RightGrid, Path=Background, Converter={StaticResource ColorSwapper}}"/>
</Grid>
Of course you will probably want to make some tweaks and cleanup according to your specific needs, but that will do it.
Here is another possible approach. If your application only has ALL dark/light backgrounds and their compliments, you can try an approach like this, using the 'ColorSwapper' that I mentioned in my other answer.
<Window>
<Window.Resources>
<local:ColorSwapper x:Key="swapper"/>
<SolidColorBrush x:Key="BackBrush" Color="Black"/>
<!--<SolidColorBrush x:Key="BackBrush" Color="White"/>-->
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{Binding Source={StaticResource BackBrush}, Converter={StaticResource swapper}}"/>
</Style>
</Window.Resources>
<Grid Background="{StaticResource BackBrush}">
<TextBlock FontSize="24" FontWeight="Bold">ABC</TextBlock>
</Grid>
</Window>
Now whenever you change the color value of 'BackBrush', all of the related foreground colors will automatically update. Based on your discussions, I think that this will also meet your style requirements. At any rate, you will likely have to make small mods to ensure that this approach will fit your exact scenario.

Custom panel with alternate background

I'd like to create a simple custom panel to layout children in a business form fashion. Ideally I'd like my markup to look like this:
<Panels:FormPanel>
<TextBlock Text="Name:"/>
<TextBox />
<TextBlock Text="Address"/>
<TextBlock Text="Unknown"/>
<TextBlock Text="City"/>
<TextBox HorizontalAlignment="Stretch"/>
<TextBlock Text="State"/>
<ComboBox/>
<TextBlock Text="Country"/>
<StackPanel>...</StackPanel>
</Panels:FormPanel>
The panel will layout controls in two columns labels on the left side and values on the right.
I have no problem laying out my controls. The problem is that I also need to alternate background for the rows to create stripes for easier reading.
Any ideas how can this be done?
This doesn't directly answer your question, but you could consider this as another solution to the underlying problem.
Take a look at http://wpg.codeplex.com/. I used a similar property-grid-like control in Windows Forms that was modified to understand custom attributes on my business objects.
Now, in WPF, I would think something similar would work really well if you follow the MVVM pattern and you decorate your ViewModel with attributes that such a property grid understands. Then you don't need to explicitly define the fields like you show above.
You could have a ViewModel:
class PersonViewModel
{
[DisplayName("Name")] // The property Grid uses this the Textblock text
[IsRequired] // The property grid could do validation on the field
[Visible]
public string Name { get; set; }
public long InvisibleSystemField { get; set; } // Not shown
}
And then you'd only have Views (Xaml files) like this:
<myCommon:PropertyGrid DataContext={Binding}/>
It could simply use it's DataContext as the starting point for reflection.
OK I'll stop there for now :)
I'm working on a WPF powered LOB application and I'll possibly build something like this in future.
Implementing a custom panel is not actually that difficult. You have to override two methods, Measure and Arrange. Google for "wpf custom panel" to get some articles about that.
What I would suggest you do to get the behavior exactly as you required in the question is extend Windows.Controls.Grid. Your custom grid could then have two columns by default that you initialize in the constructor and you can programmatically set the Grid.Column and Grid.Row properties on the child controls.
Also worth looking at could be the ItemsControl. It does have support for alternatively colored rows. This example (from MSDN) shows how to use it:
<Grid>
<Grid.Resources>
<Style x:Key="alternatingWithTriggers" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Property="ListBox.AlternationIndex" Value="1">
<Setter Property="Background" Value="CornflowerBlue"/>
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="ListBox.AlternationIndex" Value="2">
<Setter Property="Background" Value="LightBlue"/>
<Setter Property="Foreground" Value="Navy"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox AlternationCount="3" ItemsSource="{StaticResource data}"
ItemContainerStyle="{StaticResource alternatingWithTriggers}">
</ListBox>
</Grid>
You could then specify a template for the items that includes a Label and a TextBox, but getting this to work could be fiddly.
Here's one final thing I'll suggest:
XAML Powertoys include features that allow you to generate business forms from ViewModels, ViewModels from Models and much more. You might need to modify the source to get alternating row colors though.
Good luck!

How to Inherit a Control Template

I working on a WPF project where I've over-ridden the CheckBox control for some special operations. That is working correctly.
My problem is that the ControlTemplate that was applied from the theme (shinyred.xaml from codeplex), is not applied to my over-ridden control. Is there a way to inherit the CheckBox ControlTemplate for use by my new control?
All the samples the I can find are focused on inheriting the style for the CheckBox, but nothing about the ControlTemplate.
No, as you said it is possible to 'inherit' a style by using the BasedOn property, but it's not possible to directly 'inherit' a template. This is understandable though, what would be the semantics of template inheritance? How would the derived template be able to somehow add or change elements in the base template?
With styles it's entirely possible, since you can simply add Setters, Triggers, etc. The only thing that would conceivably be possible with template inheritance is adding Triggers to the base template. However, in that case you'd have to have intimate knowledge of the element names in the base template, and an element name change in the base template could break your derived one. Not to mention an issue with readability, where you refer to a name in your derived template, which is defined somewhere else entirely.
Belated Addition Having said all that, it is possible to resolve your particular problem (although I doubt by now it is still yours, or even a problem). You simply define a style for your control with a setter for the Template property thus:
<Style TargetType="<your type>">
<Setter Property="Template" Value="{StaticResource <existing template resource name>}"/>
</Style>
Keeping in mind what said by #Aviad, the following is a work around:
say you have a Button that define a template that you want to ihnerit, define your CustomButton as Custom Control like this:
public class CustomButton : Button
{
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
typeof(string), typeof(CustomButton), new UIPropertyMetadata(null));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Then go to your Generic.xaml and define the following:
<Style
x:Key="CustomButtonStyle" TargetType="{x:Type local:CustomButton}">
<Setter Property="FontSize" Value="18" /> <!--Override the font size -->
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Button Style="{StaticResource ButtonStyleBase}"
Height="{TemplateBinding Height}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:CustomButton}}, Path=Command}"
CommandParameter="{Binding}"
Width="{TemplateBinding Width}">
<Grid>
<StackPanel>
<Image Source="Image/icon.jpg" />
<TextBlock Text="{TemplateBinding Text}"></TextBlock>
</StackPanel>
</Grid>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note that the button we want to inherit the template is wrapped inside my new template, and the style is set to the existing button. go the same way with the checkbox and organize the checkbox and label for instance vertically inside the new ControlTemplate of the CustomCheckBox

Resources