I have made a custom button, since I would like to be able to set to differents strings, independantly, for the content. I have then made a ControlTemplate in my xaml, to specify how the two contents should be rendered. The content is bound to sub-properties of a selected item (from a list view). I am using INotifyPropertyChanged to notify the view, if the selection changed. And then the content and sub-content of my button, should change accordingly.
Now this works fine for the content. But the sub-content only displays anything, when the program is first loaded. After the first change in selection, it goes blank, and stays blank.
Have I missed something? Is there anything else I need to add?
Custom button C#
public class DoubleContentButton : Button
{
static DoubleContentButton() { }
public object SubContent
{
get => GetValue(SubContentProperty);
set => SetValue(SubContentProperty, value);
}
public static readonly DependencyProperty SubContentProperty =
DependencyProperty.Register(name: "SubContent", typeof(object), typeof(DoubleContentButton), new UIPropertyMetadata());
}
Custom button control template
<Style x:Key="StatusButton" TargetType="{x:Type custom:DoubleContentButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type custom:DoubleContentButton}">
<StackPanel Margin="0 0 0 20">
<!-- This updates -->
<TextBlock Text="{TemplateBinding Content}"/>
<!-- This does not update -->
<TextBlock Text="{TemplateBinding SubContent}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Implementation of button
<custom:DoubleContentButton
Command="{Binding MyCommand}"
Content="{Binding SelectedItem.Status}"
SubContent="{Binding SelectedItem.SubStatus}"
Style="{StaticResource StatusButton}" />
Can you check if your SubStatus property has a PropertyChangedEventHandler raised on the setter of the "SubStatus" or if it has, is it properly raised?
Like this:
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyRaised(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
public string SubStatus
{
get { ... }
set
{
_SubStatus = value;
OnPropertyRaised(nameof(SubStatus));
}
}
Related
I have a XamDataGrid with 2 fields. In the second field, I have toggle buttons. When the ToggleButton is clicked, the value gets set correctly. But at any time user should be able to check only one ToggleButton among all the records. The data in the set logic for the Default property works fine. But I need to set the values of Default for other items in DataSource.
Using a RadioButton instead of ToggleButton would the ideal solution. But it is possible that none of the records are default. So I want a solution to make the other default fields false when one of them becomes true.
XAML for field:
<igWPF:Field Name="Default" Width="84">
<igWPF:Field.Settings>
<igWPF:FieldSettings CellValuePresenterStyle="{StaticResource ButtonDefault}" />
</igWPF:Field.Settings>
</igWPF:Field>
XAML for CellValuePresenterStyle:
<Style x:Key="ButtonDefault" TargetType="{x:Type igWPF:CellValuePresenter}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igWPF:CellValuePresenter}">
<ToggleButton Content="Default"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"
Width="80"
Height="36"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Datasource:
public class LanguageSettingItem : INotifyPropertyChanged
{
private string name;
private bool isDefault;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return this.name; }
set { this.name = value; PropChanged("Name"); }
}
public bool Default
{
get { return this.isDefault; }
set { this.isDefault= value; PropChanged("Default"); }
}
public void PropChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You can have Toggle Button event Checked and Unchecked with same event Handler.
Then just put a if condition - (if toggle_btn.isChecked)'checked code' else 'unchecked code'
I have the following INotifyDataErrorInfo implementation in an abstract base class.
private IEnumerable<ValidationErrorModel> _validationErrors = new List<ValidationErrorModel>();
public IEnumerable<ValidationErrorModel> ValidationErrors
{
get { return _validationErrors; }
private set
{
_validationErrors = value;
OnPropertyChanged();
}
}
protected abstract Task<ValidationResult> GetValidationResultAsync();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
ValidationErrors == null)
return null;
IEnumerable<string> errors = ValidationErrors
.Where(p => p.PropertyName.Equals(propertyName))
.Select(p => p.ToString())
.ToList();
return errors;
}
public bool HasErrors
{
get
{
bool hasErrors = ValidationErrors != null && ValidationErrors.Any();
return hasErrors;
}
}
public Task<ValidationResult> ValidateAsync()
{
Task<ValidationResult> validationResultTask = GetValidationResultAsync();
validationResultTask.ContinueWith((antecedent) =>
{
if (antecedent.IsCompleted &&
!antecedent.IsCanceled &&
!antecedent.IsFaulted)
{
ValidationResult validationResult = antecedent.Result;
if (validationResult != null)
{
lock (ValidationErrors)
{
ValidationErrors =
validationResult.Errors
.Select(validationFailure =>
new ValidationErrorModel(validationFailure.PropertyName, validationFailure.ErrorMessage))
.ToList();
foreach (ValidationErrorModel validationErrorModel in ValidationErrors)
{
RaiseErrorsChanged(validationErrorModel.PropertyName);
}
}
}
}
});
return validationResultTask;
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
protected virtual void RaiseErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
{
Dispatcher.InvokeOnMainThread(() =>
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
});
}
}
In models deriving from the base class I implement the Task<ValidationResult> GetValidationResultAsync() required method, it uses fluent validation Nuget package.
private readonly ModelValidator _modelValidator = new ModelValidator();
protected override Task<ValidationResult> GetValidationResultAsync()
{
return _modelValidator.ValidateAsync(this);
}
The problem is that when I invoke from a ViewModel the ValidateAsync() method of a model the UI input controls are not invalidate/validate correctly, I actually have a tab control and validate the models in tab index changed, some might show the red border once I change tab but then again return to normal state to the next tab change.
In debug it shows that the ValidationErrors property returns errors.
My XAML input controls code is like below.
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Scheduled Date:"/>
<DatePicker DisplayDate="{Binding ScheduledDate, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
</StackPanel>
</StackPanel>
</Grid>
[Update 1]
I should mention that I use in the MainWindow a tab control and 3 tab items, each tab item is a UserControl.
I hooked up to the Validation.Error event of all the XAML UserControls and I noticed that even I get tab selected index changed value the Validation.Error fires once for the first tab and never again, I suspect there is a cleanup somewhere for a reason.
Code for the SelectedTabIndex that fires the models validations.
private int _selectedTabIndex = 0;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
_selectedTabIndex = value;
ValidateModels();
Tab2ViewModel.ValidateModels();
Tab3ViewModel.ValidateModels();
OnPropertyChanged();
}
}
The ValidateModels method calls ValidateAsync of the model in the ViewModel.
public override Task ValidateModels()
{
return Model.ValidateAsync();
}
MainWindow TabControl XAML.
<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
[Update 2]
After adding a custom error style and a custom error template, I see that the controls tooltip stay with the condition not met error but the error template is clearing. So, the TextBox shows no error template, custom or default, but the validation error exists and the tooltip shows the error.
Why the XAML templates clear on TabIndexChange and how come they don't refresh at least on the active tab item I'm viewing. This might be the problem that I should solve.
Also, as mentioned before, I don't see the ErrorsChanged revalidating the controls except the first time the SelectedTabIndex setter is invoked.
The templates I added.
<Application.Resources>
<Style x:Key="ErrorStyle"
TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel>
<Ellipse DockPanel.Dock="Right"
Margin="2,0"
ToolTip="Contains Invalid Data"
Width="10"
Height="10"
>
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="#11FF1111" Offset="0"/>
<GradientStop Color="#FFFF0000" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="4,4,15,4"/>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxErrorTemplate}"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{x:Static RelativeSource.Self}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
The problem is that tabs, expanders etc don't work well with validators, you need to include AdornerDecorator, or not use tabs which in my case is not an option.
Issue with WPF validation(IDataErrorInfo) and tab focusing.
I've got some troubles with a custom control I need to create. I try to explain you my needs first
I need to have a combobox that permits to check more than one item at time (with checkbox) but I want it to be smart enought to bind to a specific type.
I've found some MultiSelectionComboBox but none reflects my need.
Btw my main problem is that I wish to have a generic class as
public class BaseClass<T> : BaseClass
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<T>), typeof(BaseClass<T>), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(BaseClass<T>.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int i = 0;
//MultiSelectComboBox control = (MultiSelectComboBox)d;
//control.DisplayInControl();
}
public IEnumerable<T> ItemsSource
{
get { return (IEnumerable<T>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
}
public class BaseClass : Control
{
}
and a more context specific item for example
public class MultiCurr : BaseClass<Currency>
{
static MultiCurr()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiCurr), new FrameworkPropertyMetadata(typeof(MultiCurr)));
}
}
In my App.xaml I've defined a resource as
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding ItemsSource}" DisplayMemberPath="Description" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
In my MainWindow I've created an object as
<Grid>
<local:MultiCurr x:Name="test" ItemsSource="{Binding Currencies}"></local:MultiCurr>
</Grid>
and the MainWindow.cs is defined as
public partial class MainWindow : Window, INotifyPropertyChanged
{
private IList currencies;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var lst = new List<Currency>();
for (int i = 0; i < 10; i++)
{
var curr = new Currency
{
ID = i,
Description = string.Format("Currency_{0}", i)
};
lst.Add(curr);
}
Currencies = lst;
}
public IList<Currency> Currencies
{
get
{
return this.currencies;
}
set
{
this.currencies = value;
NotifyPropertyChanged("Currencies");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here's the result ...
I was wondering what am I doing wrong? is it possible what am I tring to achieve?
Thanks
UPDATE #1:
I've seen that the main problem is the datacontext of the custom usercontrol
<Application.Resources>
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding **Currencies**}" DisplayMemberPath="{Binding **DisplayMemeberPath**}" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
If I put ItemsSource as Currency (which is a property of the MainWindow) it shows.
If I put ItemsSource and DisplayMemberPath (which are defined in the BaseClass no.. how can I set the context of the usercontrol to itself?)
UPDATE #2
I've added a GoogleDrive link to the project here if anyone wants to try the solution
Thanks
Combobox is not suitable control for multiselection, because it has given behaviour, that when yo select item, Combobox closes itself. That's why Combobox doest not have SelectionMode property like ListBox. I think that ListBox inside expander is what you need.
Generic Types are not a way to go. WPF handles this different, better way. Take listbox as an example. If you bind listbox.itemssource to generic observable collection, and you try to define e.g ItemTemplate, you get full intellisense when writing bindings and warning if you bind to not existing property. http://visualstudiomagazine.com/articles/2014/03/01/~/media/ECG/visualstudiomagazine/Images/2014/03/Figure8.ashx WPF designer automatically recognizes type parameter of your observable collection. Of cousre you need to specify type of datacontext in your page by using something like this: d:DataContext="{d:DesignInstance search:AdvancedSearchPageViewModel}". However your control dont have to be and shouldn't be aware of type of items.
Following example demonstrates control that meets your requirements:
<Expander>
<Expander.Header>
<ItemsControl ItemsSource="{Binding ElementName=PART_ListBox, Path=SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Mode=OneWay}" />
<Run Text=";" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Expander.Header>
<Expander.Content>
<ListBox x:Name="PART_ListBox" SelectionMode="Multiple">
<ListBox.ItemsSource>
<x:Array Type="system:String">
<system:String>ABC</system:String>
<system:String>DEF</system:String>
<system:String>GHI</system:String>
<system:String>JKL</system:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
</Expander.Content>
</Expander>
I reccomend you to create control derived from ListBox (not usercontrol).
I have hardcoded datatemplates, but you should expose them in your custom dependency properties and use TemplateBinding in you control template. Of course you need to modify expander so it looks like combobox and ListBoxItem style so it looks like CheckBox, but it is ease.
I have ContentPresenter with DataTemplateSelector:
...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var model = item as ItemControlViewModel;
if (model.CurrentStatus == PrerequisitesStatus.Required)
{
return RequiredTemplate;
}
if (model.CurrentStatus == PrerequisitesStatus.Completed)
{
return FinishedTemplate;
}
...
return InProgressTemplate;
}
When CurrentStatus is changed, OnPropertyChanged is called.
I need somehow to trigger this DataTemplateSelector when the property is changed and change ContentPresenter DataTemplate. Any suggestions?
Threre are similar questions:
1
2, but I don't want to use any DataTriggers, because of too much states.
Tried to play with DataTriggers
<ContentPresenter
Grid.Column="1"
Height="16"
Width="16"
Margin="3">
<ContentPresenter.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource ResourceKey=_requiredStatusTemplate}" />
</DataTrigger>
</ContentPresenter.Triggers>
</ContentPresenter>
But got an error:
Triggers collection members must be of type EventTrigger :(
As you requested an example with datatriggers in the comments, here you are:
A FrameworkElement can only have EventTriggers, therefore you get the error Message Triggers collection members must be of type EventTrigger
And also don't use a ContentPresenter directly, it is meant to be used inside a ControlTemplate. Better use a ContentControl when you want to have dynamic content.
See What's the difference between ContentControl and ContentPresenter?
And finally here's a suggestion to your DataTrigger issue. I have put it inside a style for reusability ....
XAML :
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="requiredTemplate">
<TextBlock Text="requiredTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<DataTemplate x:Key="completedTemplate">
<TextBlock Text="CompletedTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<Style x:Key="selectableContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Required">
<Setter Property="ContentTemplate" Value="{StaticResource requiredTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Completed">
<Setter Property="ContentTemplate" Value="{StaticResource completedTemplate}" />
</DataTrigger>
<!-- your other Status' here -->
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Width="100" Height="100" Style="{StaticResource selectableContentStyle}"/>
</Grid>
</Window>
I could be wrong, but I believe the DataTemplateSelector is only used when the ItemContainerGenerator creates a container for an item added to the collection. Because a new container isn't generated when a property value changes, a new DataTemplate is never going to be applied via the selector.
As suggested in the comments, I would recommend you look at the VisualStateManager or data triggers, otherwise you're going to have to recreate the container for every item when one or more properties change value.
Just as an extra choice - if you want to stick to your templates, just use s binding with converter.
I came up with a behavior that would theoretically do this.
C#:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class UpdateTemplateBehavior : Behavior<ContentPresenter>
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnContentChanged));
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnValueChanged));
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public UpdateTemplateBehavior() : base() { }
protected override void OnAttached()
{
base.OnAttached();
Update();
}
void Update()
{
if (Content != null)
{
BindingOperations.ClearBinding(AssociatedObject, ContentPresenter.ContentProperty);
AssociatedObject.Content = null;
BindingOperations.SetBinding(AssociatedObject, ContentPresenter.ContentProperty, new Binding() { Path = nameof(Content), Source = this });
}
}
}
XAML:
<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}">
<i:Interaction.Behaviors>
<Behavior:UpdateTemplateBehavior Content="{Binding SomeContent}"
Value="{Binding SomeValue}"/>
</i:Interaction.Behaviors>
</ContentPresenter>
The content is "updated" (by clearing and then resetting the binding) when the content (in this example, "SomeContent") and an arbitrary value (in this example, "SomeValue") is changed, as well as when the behavior is first attached.
An update is not made unless the content is not null (my project-specific requirement). Not updating upon attaching may avoid unintentionally updating twice at once, but if the value is initially null, an update wouldn't occur until the value changes at least once.
Note: In the above example, I am not sure if the behavior has the same data context as the ContentPresenter. I use a helper class that I did not include here for brevity. Keep that in mind when testing...
I implemented a simple button with an image in it:
<Button Command="{Binding ButtonCommand, ElementName=ImageButtonControl}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ButtonImage, ElementName=ImageButtonControl}"/>
<TextBlock Text="{Binding ButtonText, ElementName=ImageButtonControl}" Margin="2,0,0,0"/>
</StackPanel>
</Button>
As you can see, I expose a ButtonCommand property in order to be able to attach an ICommand to this UserControl:
public partial class ImageButton : UserControl
{
/// <summary>
/// The dependency property that gets or sets the source of the image to render.
/// </summary>
public static DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(ImageButton));
public static DependencyProperty TextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(ImageButton));
public static DependencyProperty ButtonCommandProperty =
DependencyProperty.Register("ButtonCommand", typeof(ICommand), typeof(ImageButton));
public ImageButton()
{
this.DataContext = this;
InitializeComponent();
}
/// <summary>
/// Gets or sets the button command.
/// </summary>
public ICommand ButtonCommand
{
get { return (ICommand)GetValue(ImageButton.ButtonCommandProperty); }
set { SetValue(ImageButton.ButtonCommandProperty, value); }
}
/// <summary>
/// Gets or sets the button image.
/// </summary>
public ImageSource ButtonImage
{
get { return (ImageSource)GetValue(ImageButton.ImageSourceProperty); }
set { SetValue(ImageButton.ImageSourceProperty, value); }
}
/// <summary>
/// Gets or sets the button text.
/// </summary>
public string ButtonText
{
get { return (string)GetValue(ImageButton.TextProperty); }
set { SetValue(ImageButton.TextProperty, value); }
}
}
Then when I declare my button it gives this:
<uc:ImageButton Grid.Row="1" Grid.Column="0" ButtonCommand="{Binding AttachContextCommand}" ButtonImage="{StaticResource AssociateImage}" ButtonText="Associer"/>
And badaboom, nothing never happen when I click on my ImageButton.
When I replace the ImageButton with a simple button, the ICommand is called.
I even tried to simply extends the Button class and bind an ICommand, but once again, it didn't work...
Help appreciated !
Thx.
You can achieve this in a much cleaner way using a style and a couple of attached properties.
The attached properties will store your specific information.
The style will use these properties and build the look you want.
The element will still be a button so the command and everything else will work.
public class ImageButton
{
public static ImageSource GetImage(DependencyObject obj)
{
return (ImageSource)obj.GetValue(ImageProperty);
}
public static void SetImage(DependencyObject obj, ImageSource value)
{
obj.SetValue(ImageProperty, value);
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));
public static String GetCaption(DependencyObject obj)
{
return (String)obj.GetValue(CaptionProperty);
}
public static void SetCaption(DependencyObject obj, String value)
{
obj.SetValue(CaptionProperty, value);
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.RegisterAttached("Caption", typeof(String), typeof(ImageButton), new UIPropertyMetadata(null));
}
<Style TargetType="{x:Type Button}"
x:Key="ImageButton">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=(local:ImageButton.Image), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
<TextBlock Text="{Binding Path=(local:ImageButton.Caption), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Margin="2,0,0,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
You can then use this to declare buttons:
<Button Style="{DynamicResource ImageButton}"
local:ImageButton.Caption="Foo"
local:ImageButton.Image="..." />
Note:
I'm pretty sure it would be cleaner to go through the "Template" property and use a ControlTemplate and TemplateBindings, but that would mean re-creating the border and other stuff around your content, so if you are looking to just define a default "Content", my example would be the way to go, I think.
If the only added functionality that you want for your button is to have an image on it, then I think you're approaching this from the wrong direction. WPF is as awesome as it is because the UI controls are look-less. This means that a Control is merely a definition of functionality + some template to define how it looks. This means that the template can be swapped out at any time to change the look. Also, almost any content can be placed inside of almost any control
For instance, to define a button in your xaml that has the look your going for all you need is this:
<Window ...>
...
<Button Command="{Binding AttachContextCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{StaticResource AssociateImage}"/>
<TextBlock Text="Associer"/>
</StackPanel>
</Button>
...
</Window>
Just keep in mind that with WPF you don't have to define a new CustomControl or UserControl every time you want to change the look and feel of something. The only time you should need a CustomControl is if you want to add functionality to an existing Control or to create functionality that doesn't exist in any other Control.
Edit Due to comment:
If you're wanting to keep from defining the content for the button every time, the other option is to just have a poco (plain old CLR object) class that would define everything your interested in (I'll write my example as if you're doing this for a tool bar, because it makes sense to me):
public class ToolBarItem : INotifyPropertyChanged
{
public string Text { get ... set ... }
public ICommand Command { get ... set ... }
public ImageSource Image { get ... set ... }
}
That has a data template defined somewhere (App.xaml, Window.Resources, etc):
<DataTemplate DataType="{x:Type l:ToolBarItem}">
<Button Command="{Binding Command}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</Button>
</DataTemplate>
And then use the guy in your xaml like this:
<Window ...>
...
<ContentControl>
<ContentControl.Content>
<l:ToolBarItem Image="..." Command="..." Text="..."/>
</ContentControl.Content>
</ContentControl>
...
</Window>
I just don't know that the way you're trying to do it is the most WPF way you could do it.
EDIT Updated based on second comment
Sorry, I forgot to include the ContentControl surrounding that. Now that I remembered that, I realize that that's not much less verbose than the original where you are specifying the content manually. I'll post a new answer to help with your original question.
To re-answer the original question:
What I think you want to do is create a new CustomControl called ImageButton. Then change it to extend from Button instead of Control. You won't need a Command property since Button already has one. You'll only need to add an Image property and you can reuse the Content property from button instead of having a Text property.
When your CustomControl is created, it'll add an entry in your Generic.xaml for the default style of your ImageButton. In the Setter for the Template property you can change the ControlTemplate to this:
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<StackPanel Orientation="Horizontal">
<Image Source="{TemplateBinding Image}"/>
<ContentPresenter/>
</StackPanel>
</ControlTemplate>
Then, again, when you want to use it:
<Window ... >
...
<l:ImageButton Image="{StaticResource ...}" Command="...">
Associer
</l:ImageButton>
...
</Window>
The built-in WPF button contains code that fires the attached command in response to being clicked. Your "ImageButton" derives from UserControl, so you don't get that behavior. Probably the shortest route to get what you want is for your ImageButton class to actually derive from the WPF Button class. To accomplish that, change the markup for ImageButton from
<UserControl
...
>
...
</UserControl>
to
<Button
...
>
...
</Button>
Then change the base class of ImageButton from UserControl to Button.
You'll probably need to make some other minor changes before it all works.