I am using a custom datepicker and i am trying to change the dateformat to show Year only via a dependency property. After some experiments i got a Nullable object must have a value exception. I am using the code below:
public bool YearOnly
{
get { return (bool)this.GetValue(YearOnlyProperty); }
set { this.SetValue(YearOnlyProperty, value); }
}
public static readonly DependencyProperty YearOnlyProperty =
DependencyProperty.Register("YearOnly",
typeof(bool),
typeof(CustomizableDatePicker),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnYearOnlyChanged)));
private static void OnYearOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomizableDatePicker control = (CustomizableDatePicker)d;
control.SelectedDate = Convert.ToDateTime(control.SelectedDate.Value.ToString("yyyy"));
}
to change the date format and obviously it doesn't work.
I am using it from the DatePicker as: YearOnly="True".
On the other hand using the Style below formats the DatePicker great:
<Style TargetType="{x:Type DatePickerTextBox}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<TextBox x:Name="PART_TextBox"
Text="{Binding Path=SelectedDate, StringFormat='yyyy',
RelativeSource={RelativeSource AncestorType={x:Type DatePicker}}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Is there a way to make this work? Thanks.
After hours of testing possible solutions, i came to the conclusion that a DatePicker would never accept a DateTime that only has Year. I (short of) implemented the solution that #Clemens suggested hoping to find a solution at some point for a DatePicker that accepts YearOnly DateTimes. Anyway the code i used is bellow for anyone interested. If anyone has another solution for my problem please add an Answer to this Question.
private void CustomizableDatePicker_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{
if (!this.YearOnly)
return;
this.SetValue(SelectedYearProperty, SelectedDate.Value.Year);
Binding bnd = new Binding
{
Path = new PropertyPath("SelectedDate"),
StringFormat = ("yyyy"),
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(CustomizableDatePicker), 1)
};
this.innerTextBox.SetBinding(TextBox.CustomizableTextBox.TextProperty, bnd);
}
.................
#region YearOnly DependencyProperty
public bool YearOnly
{
get { return (bool)this.GetValue(YearOnlyProperty); }
set { this.SetValue(YearOnlyProperty, value); }
}
public static readonly DependencyProperty YearOnlyProperty =
DependencyProperty.Register("YearOnly",
typeof(bool),
typeof(CustomizableDatePicker),
new FrameworkPropertyMetadata(false));
#endregion
#region SelectedYear DependencyProperty
public int SelectedYear
{
get { return (int)this.GetValue(SelectedYearProperty); }
set { this.SetValue(SelectedYearProperty, value); }
}
public static readonly DependencyProperty SelectedYearProperty =
DependencyProperty.Register("SelectedYear",
typeof(int),
typeof(CustomizableDatePicker));
#endregion
Related
I'm trying to get all the DataGrid Expanders to open and close using a open/close button like the example below. Problem is when I'm using a single boolean to bind to this will break when I'm opening/closing a single expander.
I'm using a ListCollectionView to set the GroupingDescription.
I guess I'm looking for a solution to somehow get the Expander to play nice?
View
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Source={StaticResource proxy}, Path=Data.Expanded, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<Expander.Header>
<StackPanel>
<!-- label -->
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
ViewModel
public bool Expanded
{
get { return _expanded; }
set { _expanded = value; OnPropertyChanged(); }
}
public ListCollectionView Items
{
get
{
return _items;
}
set
{
_items = value; OnPropertyChanged();
}
}
// logic
var items = new ListCollectionView(planninglist);
items.SortDescriptions.Add(new items.GroupDescriptions.Add(new PropertyGroupDescription("AanZet"));
items.IsLiveSorting = true;
Update
Fix based on SO answer suggested in the comments by #XAMlMAX.
public class ExpanderBehavior
{
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.RegisterAttached("IsExpanded",
typeof(bool),
typeof(ExpanderBehavior),
new PropertyMetadata(OnChanged));
public static bool GetIsExpanded(DependencyObject obj)
{
return (bool)obj.GetValue(IsExpandedProperty);
}
public static void SetIsExpanded(DependencyObject obj, bool value)
{
obj.SetValue(IsExpandedProperty, value);
}
private static void OnChanged(DependencyObject o,
DependencyPropertyChangedEventArgs args)
{
Expander tb = o as Expander;
if (null != tb)
tb.IsExpanded = (bool)args.NewValue;
}
}
As per comment conversation.
Reason why you were experiencing issue with expander staying open even though it was bound is because when you use OneWay Binding and then click on the button it then becomes disconnected. To have a better idea on when it becomes disconnected use PresentationTraceSources.TraceLevel=High.
To overcome this, one can use an attached property to stop Binding from detaching.
Now this example is for ToggleButton but the logic should apply the same.
public class TBExtender
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked",
typeof(bool),
typeof(TBExtender),
new PropertyMetadata(OnChanged));
public static bool GetIsChecked(DependencyObject obj)
{
return (bool)obj.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(DependencyObject obj, bool value)
{
obj.SetValue(IsCheckedProperty, value);
}
private static void OnChanged(DependencyObject o,
DependencyPropertyChangedEventArgs args)
{
ToggleButton tb = o as ToggleButton;
if (null != tb)
tb.IsChecked = (bool)args.NewValue;
}
}
Credit goes to alex-p
and wallstreet-programmer
for responses on this SO question.
I am aware of the IsOverflowOpen and HasOverflowItems properties but I am looking for a way to tell whether the item (button, radiobutton...) has moved into the ToolBarOverflowPanel so I can use a trigger to change its style.
I need this to be able to reproduce some UWP ToolBar styles (Windows 10 Mail, Word, Excel...). I have successfully reproduced most of the style and the only missing bit is to be able to changed the style of my item when it is in the overflow panel.
On the screenshot of what I am trying to reproduce, you can clearly see that the Special Ident and Line Spacing buttons have changed style based on whether they are displayed or overflowed.
You can't do it with xaml only. You have to either use the code behind or create some attached properties.
Here is the solution with the AttachedProperty:
First you need to create an helper class exposing 2 properties:
The IsInOverflowPanel read-only property that you will use to trigger the style change.
The TrackParentPanel property, which is the enable/disable mechanism.
Here is the implementation:
public static class ToolBarHelper
{
public static readonly DependencyPropertyKey IsInOverflowPanelKey =
DependencyProperty.RegisterAttachedReadOnly("IsInOverflowPanel", typeof(bool), typeof(ToolBarHelper), new PropertyMetadata(false));
public static readonly DependencyProperty IsInOverflowPanelProperty = IsInOverflowPanelKey.DependencyProperty;
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static bool GetIsInOverflowPanel(UIElement target)
{
return (bool)target.GetValue(IsInOverflowPanelProperty);
}
public static readonly DependencyProperty TrackParentPanelProperty =
DependencyProperty.RegisterAttached("TrackParentPanel", typeof(bool), typeof(ToolBarHelper),
new PropertyMetadata(false, OnTrackParentPanelPropertyChanged));
public static void SetTrackParentPanel(DependencyObject d, bool value)
{
d.SetValue(TrackParentPanelProperty, value);
}
public static bool GetTrackParentPanel(DependencyObject d)
{
return (bool)d.GetValue(TrackParentPanelProperty);
}
private static void OnTrackParentPanelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if (element != null)
{
bool newValue = (bool)e.NewValue;
if (newValue)
{
element.LayoutUpdated += (s, arg) => OnControlLayoutUpdated(element);
}
}
}
private static void OnControlLayoutUpdated(UIElement element)
{
var isInOverflow = TreeHelper.FindParent<ToolBarOverflowPanel>(element) != null;
element.SetValue(IsInOverflowPanelKey, isInOverflow);
}
}
public static class TreeHelper
{
public static T FindParent<T>(this DependencyObject obj) where T : DependencyObject
{
return obj.GetAncestors().OfType<T>().FirstOrDefault();
}
public static IEnumerable<DependencyObject> GetAncestors(this DependencyObject element)
{
do
{
yield return element;
element = VisualTreeHelper.GetParent(element);
} while (element != null);
}
}
Then, for every items which need to change style do the following:
<Button x:Name="DeleteButton" Content="Delete" helpers:ToolBarHelper.TrackParentPanel="True">
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="helpers:ToolBarHelper.IsInOverflowPanel" Value="True">
<!-- The Overflow style setters -->
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
in my applications all inputs in textboxes have to be done by a inputbox i wrote myself. so whenever a textbox is klicked, the inputbox pops up. it looks like this:
the textbox in xaml:
<TextBox Text="{Binding Level}" Grid.Row="1" Grid.Column="2" Margin="2">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EnterLevel}" MouseAction="LeftClick" />
</TextBox.InputBindings>
</TextBox>
the command in the VM:
private void ExecuteEnterLevel()
{
var dialog = new BFH.InputBox.InputBox("Level", 1, 1, 4);
dialog.ShowDialog();
Level = Convert.ToInt16(dialog.Input);
}
so the result of the inputbox becomes the text of the inputbox. this works fine.
now my question is: can i do that for all of my textboxes that need that functionality without coding an event for every single textbox? i would like to have "myTextbox" which does it automatically.
what i tried so far:
my textbox:
class MyTextbox : TextBox
{
public MyTextbox()
{
this.MouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox("titel", "input");
dialog.ShowDialog();
tb.Text = dialog.Input;
}
}
and in xaml:
<libraries:MyTextbox/>
but MyTextbox_MouseDown is never executet. i put MessageBox.Show("test") in it without any results. i am doing something wrong i guess.
Since it is not possible to define InputBindings in styles, at least in a straight forward way. I offer to solve the issue by using AttachedProperties
lets start by defining a class for the attached property
namespace CSharpWPF
{
class EventHelper
{
public static ICommand GetLeftClick(DependencyObject obj)
{
return (ICommand)obj.GetValue(LeftClickProperty);
}
public static void SetLeftClick(DependencyObject obj, ICommand value)
{
obj.SetValue(LeftClickProperty, value);
}
// Using a DependencyProperty as the backing store for LeftClick. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LeftClickProperty =
DependencyProperty.RegisterAttached("LeftClick", typeof(ICommand), typeof(EventHelper), new PropertyMetadata(null, OnLeftClickChanged));
private static void OnLeftClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = d as FrameworkElement;
ICommand command = e.NewValue as ICommand;
if (command != null)
elem.InputBindings.Add(new MouseBinding(command, new MouseGesture(MouseAction.LeftClick)));
}
}
}
this is basically translation of mouse binding in your code done on property changed.
usage
<TextBox l:EventHelper.LeftClick="{Binding MyCommand}" />
if you wish to apply to all TextBoxes then wrap the same in a generic style targeted to TextBox
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
l: in the above refers to the name space of the above declared class eg xmlns:l="clr-namespace:CSharpWPF"
a full example
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
</StackPanel.Resources>
<TextBox />
<TextBox />
<TextBox />
<TextBox />
</StackPanel>
Attaching input box behavior
class
class InputHelper
{
public static bool GetIsInputBoxEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsInputBoxEnabledProperty);
}
public static void SetIsInputBoxEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsInputBoxEnabledProperty, value);
}
// Using a DependencyProperty as the backing store for IsInputBoxEnabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsInputBoxEnabledProperty =
DependencyProperty.RegisterAttached("IsInputBoxEnabled", typeof(bool), typeof(InputHelper), new PropertyMetadata(false, OnIsInputBoxEnabled));
private static void OnIsInputBoxEnabled(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
if ((bool)e.NewValue)
tb.PreviewMouseDown += elem_MouseDown;
else
tb.PreviewMouseDown -= elem_MouseDown;
}
static void elem_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBox tb = sender as TextBox;
var dialog = new BFH.InputBox.InputBox(tb.GetValue(InputBoxTitleProperty), tb.GetValue(InputProperty));
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public static string GetInputBoxTitle(DependencyObject obj)
{
return (string)obj.GetValue(InputBoxTitleProperty);
}
public static void SetInputBoxTitle(DependencyObject obj, string value)
{
obj.SetValue(InputBoxTitleProperty, value);
}
// Using a DependencyProperty as the backing store for InputBoxTitle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputBoxTitleProperty =
DependencyProperty.RegisterAttached("InputBoxTitle", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
public static string GetInput(DependencyObject obj)
{
return (string)obj.GetValue(InputProperty);
}
public static void SetInput(DependencyObject obj, string value)
{
obj.SetValue(InputProperty, value);
}
// Using a DependencyProperty as the backing store for Input. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputProperty =
DependencyProperty.RegisterAttached("Input", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
}
usage
<TextBox l:InputHelper.IsInputBoxEnabled="true"
l:InputHelper.InputBoxTitle="Title"
l:InputHelper.Input="Input" />
or via styles
<Style TargetType="TextBox">
<Setter Property="l:InputHelper.IsInputBoxEnabled"
Value="true" />
<Setter Property="l:InputHelper.Title"
Value="true" />
<Setter Property="l:InputHelper.Input"
Value="Input" />
</Style>
using attached properties will enable you to attach this behavior to existing classes instead of inheriting from them.
if you are inheriting the text box class you can add two properties to the same
eg
class MyTextbox : TextBox
{
public MyTextbox()
{
this.PreviewMouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox(InputBoxTitle, Input);
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public string InputBoxTitle { get; set; }
public string Input { get; set; }
}
usage
<libraries:MyTextbox InputBoxTitle="Title"
Input="Input" />
I am working a very simple lookless control, and I can't seem to get one of the template bindings to work. In the control I have two Dependency Properties, the one that is a string works, and the one that is an int does not.
The csharp code looks like this:
using System;
using System.Windows;
using System.Windows.Controls;
namespace ControlDemo
{
public class TextControlLookless : Control
{
#region Title
public static readonly DependencyProperty ChartTitleProperty =
DependencyProperty.Register("ChartTitle", typeof(string), typeof(TextControlLookless),
null);
public String ChartTitle
{
get { return (string)GetValue(ChartTitleProperty); }
set
{
SetValue(ChartTitleProperty, value);
}
}
#endregion
#region Value
public static readonly DependencyProperty ChartValueProperty =
DependencyProperty.Register("ChartValue", typeof(int), typeof(TextControlLookless),
null);
public int ChartValue
{
get { return (int)GetValue(ChartValueProperty); }
set
{
SetValue(ChartValueProperty, value);
}
}
#endregion
#region ctor
public TextControlLookless()
{
this.DefaultStyleKey = typeof(TextControlLookless);
}
#endregion
}
}
And the xaml for the control looks like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ControlDemo">
<Style TargetType="local:TextControlLookless">
<Setter Property="ChartTitle" Value="Set Title" />
<Setter Property="ChartValue" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TextControlLookless">
<Grid x:Name="Root">
<Border BorderBrush="Black" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding ChartTitle}" />
<TextBlock Text="{TemplateBinding ChartValue}" Grid.Row="1" />
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
When I put this on a page, I can see the ChartTitle (either Set Title, or whatever I set it to), but the ChartValue never shows up. If I change its type to a string, it does show up, so I must be missing something.
The problem is that TemplateBinding is a far more primitive operation than Binding. Binding is an actual class and includes some helpful features including the implicit conversion of strings back and forth between other data types.
TemplateBinding is purely a markup instruction and crucially in your case does not do type conversion for you. Hence the dependency property being bound to a Text property of a TextBlock must be a string.
You have two choices:-
One choice is instead using TemplateBinding give the TextBlock a name and assign its Text in the ChartValue property changed call back:-
#region Value
public static readonly DependencyProperty ChartValueProperty =
DependencyProperty.Register("ChartValue", typeof(int), typeof(TextControlLookless),
new PropertyMetadata(0, OnChartValuePropertyChanged));
private static void OnChartValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextControlLookless source = d as TextControlLookless;
source.Refresh();
}
public int ChartValue
{
get { return (int)GetValue(ChartValueProperty); }
set
{
SetValue(ChartValueProperty, value);
}
}
#endregion
private TextBlock txtChartValue { get; set; }
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
txtChartValue = GetTemplateChild("txtChartValue") as TextBlock;
Refresh();
}
private void Refresh()
{
if (txtChartValue != null)
{
txtChartValue.Text = ChartValue.ToString();
}
}
where the xaml looks like:-
<TextBlock x:Name="txtChartValue" Grid.Row="1" />
The other choice is to create a private dependency property for the value with type of string:-
#region Value
public static readonly DependencyProperty ChartValueProperty =
DependencyProperty.Register("ChartValue", typeof(int), typeof(TextControlLookless),
new PropertyMetadata(0, OnChartValuePropertyChanged));
private static void OnChartValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetValue(ChartValueStrProperty, e.NewValue.ToString());
}
private static readonly DependencyProperty ChartValueStrProperty =
DependencyProperty.Register("ChartValueStr", typeof(string), typeof(TextControlLookless),
new PropertyMetadata("0"));
public int ChartValue
{
get { return (int)GetValue(ChartValueProperty); }
set
{
SetValue(ChartValueProperty, value);
}
}
#endregion
where the xaml looks like:-
<TextBlock Text="{TemplateBinding ChartValueStr}" Grid.Row="1" />
Note that the ChartValueStrProperty is private and I haven't bothered creating a standard .NET property to cover it. TemplateBinding actually takes the property name you assign suffixes is with "Property" then looks for a static field on the target type.
Both approaches have their strengths and weaknesses. The first approach is the more common pattern but takes a little more code and is less flexiable (the control displaying the value must be a TextBlock). The second is more flexiable and uses less code but is somewhat unorthodox.
I have a usercontrol that expose a public property like this :
public Double ButtonImageHeight
{
get { return imgButtonImage.Height; }
set { imgButtonImage.Height = value; }
}
when I use that control, I want to be able to set that property throught a Style like that :
<Style x:Key="MyButtonStyle" TargetType="my:CustomButtonUserControl" >
<Setter Property="ButtonImageHeight" Value="100" />
</Style>
What am I doing wrong?
Thanks
thanks Matt, I just found it myself but you were absolutely right... here's the exact code I used in case it can help someone else (all the examples I found were on WPF, silverlight is just slightly different) :
public static readonly DependencyProperty ButtonImageHeightProperty = DependencyProperty.Register("ButtonImageHeight", typeof(Double), typeof(CustomButtonUserControl),new PropertyMetadata(ButtonImageHeight_PropertyChanged ));
public Double ButtonImageHeight
{
get { return (Double)GetValue(ButtonImageHeightProperty); }
set { SetValue(ButtonImageHeightProperty, value); }
}
private static void ButtonImageHeight_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((CustomButtonUserControl)source).imgButtonImage.Height = (Double)e.NewValue;
}
The property needs to be a dependency property in order to support styles.
You can make it even more generic and nice by passing through a Style for your imgButtonImage, that way you can set multiple properties. So within your user control add the dependency property, but make it a Style:
public static readonly DependencyProperty UseStyleProperty =
DependencyProperty.Register("UseStyle", typeof(Style), typeof(CustomButtonUserControl), new PropertyMetadata(UseStyle_PropertyChanged));
public Style UseStyle
{
get { return (Style)GetValue(UseStyleProperty); }
set { SetValue(UseStyleProperty, value); }
}
private static void UseStyle_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((CustomButtonUserControl)source).imgButtonImage.Style = (Style)e.NewValue;
}
Notice how within the PropertyChanged function I set the style of the control to the new style.
Then when I host the UserControl I can pass through the style:
<Style x:Name="MyFancyStyle" TargetType="Button" >
<Setter Property="FontSize" Value="24" />
</Style>
<controls:MyUserControl UseStyle="{StaticResource MyFancyStyle}" />
works in VS design mode too! (It's a miracle)