This question already has answers here:
DependencyProperty not triggered
(2 answers)
What's the difference between Dependency Property SetValue() & SetCurrentValue()
(3 answers)
Closed 11 months ago.
I have a sets of user controls in my WPF application. One of them I've listed in code below:
RichSlider.xaml - control directly
<UserControl x:Name="RichSliderControl"
x:Class="CustomControls.RichSlider"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Controls;component/Themes/RichSlider.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
</UserControl>
RichSlider.xaml - control style
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControls="clr-namespace:CustomControls"
>
<Style x:Key="GlobalLabelStyle" TargetType="{x:Type FrameworkElement}">
<Setter Property="Margin" Value="0"/>
<Setter Property="TextElement.FontSize" Value="14"/>
<Setter Property="TextElement.FontWeight" Value="Bold"/>
<Setter Property="TextElement.FontFamily" Value="Century Gothic"/>
</Style>
<Style x:Key="ValueLabelStyle" TargetType="{x:Type Label}" BasedOn="{StaticResource GlobalLabelStyle}">
<Setter Property="Content" Value="{Binding Value, ElementName=RichSliderControl, Mode=OneWay}"/>
</Style>
<Style TargetType="{x:Type Slider}">
<Setter Property="Margin" Value="0"/>
<Setter Property="Foreground" Value="Gray"/>
<Setter Property="Background" Value="#F5F5F5"/>
<Setter Property="IsSnapToTickEnabled" Value="True"/>
<Setter Property="Minimum" Value="{Binding Minimum, ElementName=RichSliderControl}"/>
<Setter Property="Maximum" Value="{Binding Maximum, ElementName=RichSliderControl}"/>
<Setter Property="Value" Value="{Binding Value, ElementName=RichSliderControl}"/>
<Setter Property="SmallChange" Value="{Binding SmallChange, ElementName=RichSliderControl}"/>
<Setter Property="LargeChange" Value="{Binding LargeChange, ElementName=RichSliderControl}"/>
</Style>
<ControlTemplate x:Key="HorizontalSlider" TargetType="{x:Type UserControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalContentAlignment="Center" Content="{Binding Text, ElementName=RichSliderControl, Mode=OneWay}"></Label>
<Label Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" VerticalContentAlignment="Center" Style="{StaticResource ValueLabelStyle}"></Label>
<Slider x:Name="ValueSlider"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
TickPlacement="BottomRight"
>
</Slider>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="VerticalSlider" TargetType="{x:Type UserControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="26"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center">
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Text="{Binding Text, ElementName=RichSliderControl, Mode=OneWay}"></TextBlock>
</Label>
<Label Grid.Row="1" Grid.Column="1" HorizontalContentAlignment="Center" Style="{StaticResource ValueLabelStyle}"></Label>
<Slider x:Name="ValueSlider"
Grid.Row="0"
Grid.Column="1"
Orientation="Vertical"
TickPlacement="TopLeft"
IsDirectionReversed="True"
>
</Slider>
</Grid>
</ControlTemplate>
<Style TargetType="{x:Type CustomControls:RichSlider}">
<Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="MinWidth" Value="100" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="Template" Value="{StaticResource HorizontalSlider}" />
</Trigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="MinWidth" Value="20" />
<Setter Property="MinHeight" Value="100" />
<Setter Property="Template" Value="{StaticResource VerticalSlider}" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
RichSlider.xaml.cs - control code
public partial class RichSlider : UserControl
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(RichSlider));
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty SmallChangeProperty =
DependencyProperty.Register("SmallChange", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty LargeChangeProperty =
DependencyProperty.Register("LargeChange", typeof(double), typeof(RichSlider));
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(RichSlider));
private static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<double>), typeof(RichSlider));
[Bindable(true)]
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
[Bindable(true)]
public double Value
{
get => (double)GetValue(ValueProperty);
set
{
var val = value;
if (val < Minimum)
{
val = Minimum;
}
if (val > Maximum)
{
val = Maximum;
}
SetValue(ValueProperty, val);
RaiseEvent(new(ValueChangedEvent));
}
}
[Bindable(true)]
public double Minimum
{
get => (double)GetValue(MinimumProperty);
set
{
var min = value;
if (Value < min)
{
Value = min;
}
if (Maximum < min)
{
Maximum = min;
}
SetValue(MinimumProperty, min);
}
}
[Bindable(true)]
public double Maximum
{
get => (double)GetValue(MaximumProperty);
set
{
var max = value;
if (Value > max)
{
Value = max;
}
if (max < Minimum)
{
max = Minimum;
}
SetValue(MaximumProperty, max);
}
}
[Bindable(true)]
public double SmallChange
{
get => (double)GetValue(SmallChangeProperty);
set
{
var change = value;
if (change > LargeChange)
{
change = LargeChange;
}
SetValue(SmallChangeProperty, change);
}
}
[Bindable(true)]
public double LargeChange
{
get => (double)GetValue(LargeChangeProperty);
set
{
var change = value;
if (change < SmallChange)
{
change = SmallChange;
}
SetValue(LargeChangeProperty, change);
}
}
[Bindable(true)]
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public RichSlider()
{
InitializeComponent();
}
public event RoutedPropertyChangedEventHandler<double> ValueChanged
{
add
{
AddHandler(ValueChangedEvent, value);
}
remove
{
RemoveHandler(ValueChangedEvent, value);
}
}
public override void OnApplyTemplate()
{
if (GetTemplateChild("ValueSlider") is Slider slider)
{
slider.MouseWheel += new MouseWheelEventHandler(ValueSlider_MouseWheel);
slider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(ValueSlider_ValueChanged);
}
base.OnApplyTemplate();
}
private void ValueSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
RaiseEvent(new(ValueChangedEvent));
}
private void ValueSlider_MouseWheel(object sender, MouseWheelEventArgs e)
{
var directionReversed = (sender as Slider).IsDirectionReversed;
if (((e.Delta > 0 && !directionReversed) || (e.Delta < 0 && directionReversed)) && Value < Maximum)
{
Value++;
}
else if (((e.Delta < 0 && !directionReversed) || (e.Delta > 0 && directionReversed)) && Value > Minimum)
{
Value--;
}
}
}
Ok. I'm adding this control to my window.xaml and binding some value like:
xmlns:CustomControls="clr-namespace:CustomControls;assembly=Controls"
....
<CustomControls:RichSlider Minimum="1" Maximum="5" SmallChange="1" Orientation="Horizontal" Value="{Binding Path=Acceleration, Mode=OneWay}" Text="{DynamicResource Acceleration}"></CustomControls:RichSlider>
And I have a ListBox of items, which contains Name and Acceleration properties. So, when I'm clicking on ListBoxItem, the value in my RichSlider is updating according to the value of Acceleration property in item. So, seems like the binding is working ok.
But no. When I'm changing the value in my RichSlider manually, the value is no longer updated for all other items and is stuck with the value I set. As evidence:
<TextBox Text="{Binding Path=Name, Mode=OneWay}"></TextBox>
The Text value is updating after selecting another item, not depends from changing I made. I also checked the way like:
<TextBox Text="{Binding Path=Acceleration, Mode=OneWay}"></TextBox>
and it's also works fine. So, this way I made the conclusion that the issue is in my control, not in Binding, cause for TextBox it works, but for RichSlider - not.
So, does anyone have any thoughts what I'm doing wrong?
Related
how to make part of list item selectable wpf and other not selectable
I have an list view can select and unselect item in its and want when unselect its by part of the item
Class for Select and unselect items in list view
public static class ListBoxSelectionBehavior
{
public static readonly DependencyProperty ClickSelectionProperty =
DependencyProperty.RegisterAttached("ClickSelection",
typeof(bool),
typeof(ListBoxSelectionBehavior),
new UIPropertyMetadata(false, OnClickSelectionChanged));
public static bool GetClickSelection(DependencyObject obj)
{
return (bool)obj.GetValue(ClickSelectionProperty);
}
public static void SetClickSelection(DependencyObject obj, bool value)
{
obj.SetValue(ClickSelectionProperty, value);
}
private static void OnClickSelectionChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
{
ListView listBox = dpo as ListView;
if (listBox != null)
{
if ((bool)e.NewValue == true)
{
listBox.SelectionMode = SelectionMode.Multiple;
listBox.SelectionChanged += OnSelectionChanged;
}
else
{
listBox.SelectionChanged -= OnSelectionChanged;
}
}
}
static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
ListView listBox = sender as ListView;
var valid = e.AddedItems[0];
foreach (var item in new ArrayList(listBox.SelectedItems))
{
if (item != valid)
{
listBox.SelectedItems.Remove(item);
}
}
}
}
}
Class for an extention of wrap panel to add discription and every items
public class WrapPaneEx : WrapPanel
{
protected override Size ArrangeOverride(Size finalSize)
{
var size = base.ArrangeOverride(finalSize);
foreach (UIElement fe in this.Children)
{
var itemLocation = GetItemLocation(fe);
if (itemLocation == null)
{
itemLocation = new ItemLocation(this, fe);
SetItemLocation(fe, itemLocation);
}
itemLocation.OnLocationPropertyChanged();
}
return size;
}
public static ItemLocation GetItemLocation(DependencyObject obj)
{
return (ItemLocation)obj.GetValue(ItemLocationProperty);
}
public static void SetItemLocation(DependencyObject obj, ItemLocation value)
{
obj.SetValue(ItemLocationProperty, value);
}
public static readonly DependencyProperty ItemLocationProperty = DependencyProperty.RegisterAttached("ItemLocation", typeof(ItemLocation), typeof(WrapPaneEx), new PropertyMetadata(null));
}
public class ItemLocation : INotifyPropertyChanged
{
public ItemLocation(Panel panel, UIElement itemContainer)
{
this._Panel = panel;
this._ItemContainer = itemContainer;
}
private UIElement _ItemContainer;
private Panel _Panel;
public Point? Location
{
get
{
if (_Location == null && _Panel != null && _ItemContainer != null)
{
_Location = _ItemContainer.TranslatePoint(default(Point), _Panel);
}
return _Location;
}
}
private Point? _Location;
public Point? LocationN
{
get
{
if (_LocationN == null && _Location == null && _Panel != null && _ItemContainer != null)
{
Point? np = Location;
if (np != null)
{
_LocationN = new Point(-np.Value.X, -np.Value.Y);
}
}
return _LocationN;
}
}
private Point? _LocationN;
public event PropertyChangedEventHandler PropertyChanged;
internal void OnLocationPropertyChanged()
{
_Location = null;
_LocationN = null;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Location)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocationN)));
}
}
I hop when unselect item on list view to not be not selectable when click on the canvas of the details
<ScrollViewer x:Name="scollviewer" HorizontalAlignment="Stretch" >
<ListView x:Name="listview" Main:ListBoxSelectionBehavior.ClickSelection="True" HorizontalAlignment="Center" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedMovie}" PreviewMouseWheel="List_PreviewMouseWheel">
<ListView.Resources>
<ControlTemplate TargetType="{x:Type ListViewItem}" x:Key="withDetailTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="0" SnapsToDevicePixels="true">
<Grid>
<local:MovieListItemControl/>
<Border Background="White" Visibility="Collapsed"
Height="2" Grid.RowSpan="2" x:Name="indicator"
VerticalAlignment="Bottom"/>
</Grid>
</Border>
<!-- **************** -->
<Canvas Grid.Row="1" x:Name="detailCanvas"
Width="0" selec
Height="{Binding ElementName=detailGrid,Path=ActualHeight}"
HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="Collapsed">
<Grid x:Name="detailGrid" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ScrollViewer},AncestorLevel=2},Path=ActualWidth}"
Canvas.Left="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListViewItem}},Path=(local:WrapPaneEx.ItemLocation).LocationN.X}">
<local:MovieDetailsControl/>
</Grid>
</Canvas>
<!-- **************** -->
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="Black"/>
<Setter Property="Opacity" TargetName="Bd" Value="0.7"/>
<Setter TargetName="indicator" Property="Visibility" Value="Visible"/>
<Setter TargetName="detailCanvas" Property="Visibility" Value="Visible"/>
</Trigger>
<EventTrigger RoutedEvent="Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template" Value="{StaticResource withDetailTemplate}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<local:WrapPaneEx HorizontalAlignment="Center" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ScrollViewer},AncestorLevel=2},Path=ActualWidth}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</ScrollViewer>
image for expected propgram
If you want to make some elements in your ListView be readonly, you should use the IsHitTestVisible="False" Property on the ListViewIitem. To put that into perspective one would create a style for the ListViewIem and then use DataTrigger to switch that off and on.
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="Property" Value="False">
<Setter Property="IsHitTestVisible" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
Because this is a DependencyProperty you could also directly Bind to that Property:
<Style TargetType="ListViewItem">
<Setter Property="IsHitTestVisible" Value="{Binding Property}"/>
</Style>
MSDN explanation.
And here is brief explanation:
Gets or sets a value that declares whether this element can possibly be returned as a hit test result from some portion of its rendered content. This is a dependency property.
I am very new to WPF world so do not have much ideas about doing it. Basically,
I want to check whether value entered into textboxes are double number or not.If the value is double number then change the result textbox value to NAN and also change the color of input textbox to red.
Can anyone please guide me how to accomplish this?
Model.cs
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
{
if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue)) && field.Equals(newValue)) return;
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract class ViewModelBase : ObservableBase
{
public bool IsInDesignMode
=> (bool)DesignerProperties.IsInDesignModeProperty
.GetMetadata(typeof(DependencyObject))
.DefaultValue;
}
ViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (IsInDesignMode)
{
valueA = 2;
valueB = 3;
Calc();
}
}
#region Properties
private int valueA;
public int ValueA
{
get => valueA;
set
{
Set(ref valueA, value);
Calc();
}
}
private int valueB;
public int ValueB
{
get => valueB;
set
{
Set(ref valueB, value);
Calc();
}
}
private int valueC;
public int ValueC
{
get => valueC;
set => Set(ref valueC, value);
}
private int valueD;
public int ValueD
{
get => valueD;
set => Set(ref valueD, value);
}
#endregion
#region Methods
private void Calc()
{
ValueC = valueA + valueB;
ValueD = valueA * valueB;
}
#endregion
}
XAML
<Window x:Class="WPFTestApplication.MainWindow"
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:WPFTestApplication.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBox" x:Key="TextBox">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
<Setter Property="Grid.Column" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Silver" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox" x:Key="TextBoxA" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox" x:Key="TextBoxB" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightGreen" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource TextBox}"/>
</Grid.Resources>
<TextBlock Text="Value A"/>
<TextBox Text="{Binding ValueA, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxA}"/>
<TextBlock Text="Value B" Grid.Row="1"/>
<TextBox Text="{Binding ValueB, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxB}"
Grid.Row="1"/>
<TextBlock Text="Value C" Grid.Row="2"/>
<TextBox Text="{Binding ValueC}"
IsReadOnly="True"
Grid.Row="2"/>
<TextBlock Text="Value D" Grid.Row="3"/>
<TextBox Text="{Binding ValueD}"
IsReadOnly="True"
Grid.Row="3"/>
</Grid>
</Window>
The easiest way for your problem is to implement a ValidationRule.
Here is the code for the rule:
public class DoubleValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//You can do whatever you want here
double check;
if (!double.TryParse(value.ToString(),out check))
{
//ValidationResult(false,*) => in error
return new ValidationResult(false, "Please enter a number");
}
//ValidationResult(true,*) => is ok
return new ValidationResult(true, null);
}
}
Then in your XAML you have to refer to this ValidationRule when binding, which allows you to get the Validation.HasError property in your style.
<TextBox Validation.ErrorTemplate="{x:Null}">
<TextBox.Text>
<Binding Path="ValueB" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DoubleValidation/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<TextBox.Style>
<Style BasedOn="{StaticResource TextBoxB}" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Since the Error will add a red border to the TextBox, I add Validation.ErrorTemplate="{x:Null}" to keep full control.
If you want to change the Textbox value to NaN, you should do it in your ViewModel. But I would disrecommend that, since it's very boring for the user to see its inputs getting change by the UI.
I have modified your code to achieve your goal:
Model.cs
I have added ObservableBase.NotifyPropertyChanged()
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
{
if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue)) && field.Equals(newValue)) return;
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public abstract class ViewModelBase : ObservableBase
{
public bool IsInDesignMode
=> (bool)DesignerProperties.IsInDesignModeProperty
.GetMetadata(typeof(DependencyObject))
.DefaultValue;
}
Then your ViewModel would look like this, you see I changed the types from int to string, then added validation flags, the trick to check whether the input is double or not is to use double.TryParse.
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
valueAisValid = true;
valueBisValid = true;
if (IsInDesignMode)
{
Calc();
}
}
#region Properties
private string valueA;
public string ValueA
{
get => valueA;
set
{
if (!string.IsNullOrEmpty(value))
{
Set(ref valueA, value);
Set(ref valueAisValid, double.TryParse(ValueA, out double d));
NotifyPropertyChanged(nameof(ValueAIsValid));
Calc();
}
}
}
private bool valueAisValid;
public bool ValueAIsValid => valueAisValid;
private string valueB;
public string ValueB
{
get => valueB;
set
{
if (!string.IsNullOrEmpty(value))
{
Set(ref valueB, value);
Set(ref valueBisValid, double.TryParse(ValueB, out double d));
NotifyPropertyChanged(nameof(ValueBIsValid));
Calc();
}
}
}
private bool valueBisValid;
public bool ValueBIsValid => valueBisValid;
private string valueC;
public string ValueC
{
get => valueC;
set => Set(ref valueC, value);
}
private string valueD;
public string ValueD
{
get => valueD;
set => Set(ref valueD, value);
}
public bool InputsValid => ValueAIsValid && ValueBIsValid;
#endregion
#region Methods
private void Calc()
{
if (InputsValid)
{
double sum = Convert.ToDouble(valueA) + Convert.ToDouble(valueB);
double product = Convert.ToDouble(valueA) * Convert.ToDouble(valueB);
ValueC = sum.ToString(CultureInfo.InvariantCulture);
ValueD = product.ToString(CultureInfo.InvariantCulture);
}
else
{
ValueC = "NAN";
ValueD = "NAN";
}
}
#endregion
}
Now here is the new guy, meet BoolToBackgroundColorConverter.
namespace WPFTestApplication
{
public class BoolToBackgroundColorConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && !(bool)value)
{
return new SolidColorBrush(Colors.Red);
}
else if(value != null && (bool)value && parameter != null)
{
return (SolidColorBrush)parameter;
}
else
{
return new SolidColorBrush(Colors.White);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Now your xaml would look like:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:BoolToBackgroundColorConverter x:Key="BoolToBackgroundColorConverter"/>
</Window.Resources>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<SolidColorBrush x:Key="LightGreen" Color="LightGreen" />
<SolidColorBrush x:Key="LightBlue" Color="LightBlue" />
<SolidColorBrush x:Key="White" Color="White" />
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBox" x:Key="TextBox">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
<Setter Property="Grid.Column" Value="1"/>
</Style>
<Style TargetType="TextBox" x:Key="TextBoxA" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding ValueAIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource LightBlue}}" />
</Trigger>
</Style.Triggers>
<Setter Property="Background" Value="{Binding ValueAIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource White}}" />
</Style>
<Style TargetType="TextBox" x:Key="TextBoxB" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding ValueBIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource LightGreen}}" />
</Trigger>
</Style.Triggers>
<Setter Property="Background" Value="{Binding ValueBIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource White}}" />
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource TextBox}"/>
</Grid.Resources>
<TextBlock Text="Value A"/>
<TextBox Text="{Binding ValueA, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxA}"/>
<TextBlock Text="Value B" Grid.Row="1"/>
<TextBox Text="{Binding ValueB, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxB}"
Grid.Row="1"/>
<TextBlock Text="Value C" Grid.Row="2"/>
<TextBox Text="{Binding ValueC}"
IsReadOnly="True"
Grid.Row="2"/>
<TextBlock Text="Value D" Grid.Row="3"/>
<TextBox Text="{Binding ValueD}"
IsReadOnly="True"
Grid.Row="3"/>
</Grid>
Output:
Hope this helps!
The easiest way to do this is to handle the validation in the View layer - using a control such as DoubleUpDown from the Extended WPF Toolkit, rather than trying to validate in the ViewModel by parsing a text string.
I have a Grid, and in that grid, I have this:
<StackPanel Grid.Row="2"
Grid.Column="0">
<Grid x:Name="GridButtonItem" Margin="30,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background"
Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
Value="#332a8dd4" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="False">
<Setter Property="Background"
Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Margin="3"
Source="{dx:DXImageOffice2013 Image=Windows_32x32.png}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Margin="10,3,3,0"
Text="Application Log" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Margin="10,0,3,3"
Text="C:\Program Files (x86)\ATI Technologies\ATI.ACE\MOM-InstallProxy" />
</Grid>
</StackPanel>
The StackPanel is actually meant to hold many of the GridButtonItem items. Is there a way that I can somehow make a "template" of GridButtonItem and then for each one I want to add to the StackPanel, just set the Image and Text properties?
Something like this (just pseudo-code for demonstration):
<StackPanel>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
</StackPanel>
So each one that is added picks up the row/column definitions, and an embedded Image and two TextBlocks. Then I just set the three properties for each one added.
Is this possible?
You can put your grid control into a UserControl and then reuse the UserControl throughout your project. I have a simple example of doing this with a label and Textbox.
here is the XAML:
<UserControl x:Class="TestVision.CustomControls.LabelAndTextbox"
x:Name="parent"
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:TestVision.CustomControls"
mc:Ignorable="d" >
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=parent}">
<TextBlock Text="{Binding Path=Label}" Width="{Binding Path=LabelWidth}" VerticalAlignment="Center" TextAlignment="Right" Margin="0,0,10,0" Height="22"/>
<TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" Width="{Binding Path=TextboxWidth}" IsReadOnly="{Binding Path=TextboxReadOnly, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="{Binding Path=TextboxHorizontalContentAlgnment}"/>
</StackPanel>
</UserControl>
Any properties that you want to be able to set e.g. your image text etc. must be bound to Dependency Properties in the code behind.
Code behind:
public partial class LabelAndTextbox : UserControl
{
/// <summary>
/// Gets or sets the Label which is displayed next to the field
/// </summary>
public String Label
{
get { return (String)GetValue(LabelContent); }
set { SetValue(LabelContent, value); }
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContent =
DependencyProperty.Register("Label", typeof(string),
typeof(LabelAndTextbox), new PropertyMetadata(""));
public object Text
{
get { return (object)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(object),
typeof(LabelAndTextbox), new PropertyMetadata(null));
public Double LabelWidth
{
get { return (Double)GetValue(LabelWidthProperty); }
set { SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register("LabelWidth", typeof(Double),
typeof(LabelAndTextbox), new PropertyMetadata());
public Double TextboxWidth
{
get { return (Double)GetValue(TextboxWidthProperty); }
set { SetValue(TextboxWidthProperty, value); }
}
public static readonly DependencyProperty TextboxWidthProperty =
DependencyProperty.Register("TextboxWidth", typeof(Double),
typeof(LabelAndTextbox), new PropertyMetadata());
public bool TextboxReadOnly
{
get { return (bool)GetValue(TextboxReadOnlyProperty); }
set { SetValue(TextboxReadOnlyProperty, value); }
}
public static readonly DependencyProperty TextboxReadOnlyProperty =
DependencyProperty.Register("TextboxReadOnly", typeof(bool),
typeof(LabelAndTextbox), new FrameworkPropertyMetadata());
public HorizontalAlignment TextboxHorizontalContentAlgnment
{
get { return (HorizontalAlignment)GetValue(TextboxHorizontalContentAlgnmentProperty); }
set { SetValue(TextboxHorizontalContentAlgnmentProperty, value); }
}
public static readonly DependencyProperty TextboxHorizontalContentAlgnmentProperty =
DependencyProperty.Register("TextboxHorizontalContentAlgnment", typeof(HorizontalAlignment),
typeof(LabelAndTextbox), new FrameworkPropertyMetadata());
public LabelAndTextbox()
{
InitializeComponent();
}
}
you then will need to add a reference in the XAML file to your UserControl like this:
xmlns:Resource="clr-namespace:ProjectNamespace.FolderContainingYourControl"
Resource is a generic identifier you can call it what you like, you can then reference your control in the like this:
<Resource:LabelAndTextblock x:Name="AddressLine1" Label="{Binding LblTxt_AddressLine1}" Text="{Binding AddressLine1, Mode=TwoWay}" Margin="10,5,0,5" LabelWidth="70" TextWidth="250" TextHeight="60"/>
You could do this with a UserControl (two different ways) or a DataTemplate. Let's go with DataTemplate, because stuicidle already ably demonstrated one UserControl approach.
There are a couple of different ways to do this with a DataTemplate, too.
We're going to do something called an implicit DataTemplate. It's created in Resources, but it has no x:Key property, just a DataType="{x:Type local:GridItemViewModel}" property. What that will do is this: Wherever that DataTemplate is in scope, whenever XAML needs to display a GridItemViewModel and nothing is specifying a template to display it in, it'll use that implicit template.
Clear as mud! Welcome to the XAML learning curve.
ViewModels.cs
using System;
using System.ComponentModel;
using System.Windows.Media;
namespace GridItemAnswer
{
#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class
#region GridItemViewModel Class
public class GridItemViewModel : ViewModelBase
{
#region LabelText Property
private String _labelText = null;
public String LabelText
{
get { return _labelText; }
set
{
if (value != _labelText)
{
_labelText = value;
OnPropertyChanged();
}
}
}
#endregion LabelText Property
#region Path Property
private String _path = null;
public String Path
{
get { return _path; }
set
{
if (value != _path)
{
_path = value;
OnPropertyChanged();
}
}
}
#endregion Path Property
#region ImageSource Property
private ImageSource _imageSource = null;
public ImageSource ImageSource
{
get { return _imageSource; }
set
{
if (value != _imageSource)
{
_imageSource = value;
OnPropertyChanged();
}
}
}
#endregion ImageSource Property
}
#endregion GridItemViewModel Class
}
MainWindow.xaml
<Window
x:Class="GridItemAnswer.MainWindow"
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:GridItemAnswer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<DataTemplate DataType="{x:Type local:GridItemViewModel}">
<StackPanel>
<Grid x:Name="GridButtonItem" Margin="30,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#332a8dd4" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Margin="3"
Source="{Binding Image}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="10,3,3,0"
Text="{Binding LabelText}"
/>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="10,0,3,3"
Text="{Binding Path}"
/>
</Grid>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ItemsControl>
<local:GridItemViewModel
LabelText="Foo Bar"
Path="c:\foo\bar"
/>
<local:GridItemViewModel
LabelText="Baz Planxty"
Path="c:\baz\planxty"
/>
</ItemsControl>
<Label>
<local:GridItemViewModel
LabelText="A frog walks into a bank asking for a loan"
Path="c:\knick\knack"
/>
</Label>
</StackPanel>
</Grid>
</Window>
My ListView is bound to an ObservableCollection, is there a way to position a button after the last listviewitem? What I have done is define the button in the DataTemplate like below:
<DataTemplate x:Key="TestDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="SeletedFilterText" Text="{Binding}" />
<Button Command="{Binding DataContext.TestCommand,ElementName=TestListView}"
Content="Test"
Visibility="{Binding Converter={StaticResource testConverter}}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
In my ViewModel, I define a string variable to store the last item. The ItemSource(an Observable) may add or remove item, every time I set the last of the Collection to the LastItem variable. In the converter, compare the binding content with the LastItem, if the value is true, display the Button, if false, hide it. But the converter will never be triggered. Anyone can help?
I would suggest not to have backup field in ViewModel to keep track of lastItem in collection.
You can do that with only Converter in place which will return true or false if passed ListViewItem is last item in ListView or not.
In case you want to call the converter whenever underlying ObservableCollection add/remove item in it, I would suggest to pass Count property to converter so that converter gets fired whenever item is added/removed from collection.
Converter code:
public class IsLastItemInContainerConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
CultureInfo culture)
{
DependencyObject item = (DependencyObject)values[0];
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
if (ic != null)
{
return ic.ItemContainerGenerator.IndexFromContainer(item)
== ic.Items.Count - 1;
}
else
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Button Content="Test">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource IsLastItemInContainerConverter}">
<Binding Path="."
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=ListViewItem}"/>
<Binding Path="DataContext.SourceCollection.Count"
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=ListView}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Replace SourceCollection with your ObservableCollection name in dataTrigger.
I would suggest you create a custom control for your use case. Like so:
public class ButtonListView : ListView
{
static ButtonListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ButtonListView), new FrameworkPropertyMetadata(typeof(ButtonListView)));
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof (ICommand), typeof (ButtonListView), new PropertyMetadata(default(ICommand)));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(
"ButtonContent", typeof (object), typeof (ButtonListView), new PropertyMetadata(default(object)));
public object ButtonContent
{
get { return (object) GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
}
And use this style:
<SolidColorBrush x:Key="ListBox.Disabled.Background" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ListBox.Disabled.Border" Color="#FFD9D9D9" />
<Style TargetType="{x:Type local:ButtonListView}" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonListView}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true"
Padding="1">
<ScrollViewer Padding="{TemplateBinding Padding}"
Focusable="false">
<StackPanel>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Button Content="{TemplateBinding ButtonContent}" Command="{TemplateBinding Command}"></Button>
</StackPanel>
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBox.Disabled.Background}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBox.Disabled.Border}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true" />
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then use it like so:
<wpfSandbox:ButtonListView ButtonContent="Press" Command="{Binding ...}"/>
This way you don't need to Keep track of the order in the ObservableCollection
Yes and it's very easy:
Define your Observable collection with generic type of <DependencyObject>
Add a custom object to the end of the collection. (it can be a something like a ViewModel for Button if you want to add commands or etc to it)
Don't set the ItemTemplate of your ListView (or ItemsControl or etc)
Instead, define two DataTemplates without x:Key in the resources and set their DataType to the desired types. It should be like "{x:Type local:ButtonVm}" or "{x:Type vm:ListViewItemType}"
Now the template for each item automatically set to the data template that matches the type of that item.
Example:
(note that you can move ListView.Resources to Window.Resources if the templates can be reused elsewhere)
MainWindow.xaml:
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:ListItemVm}">
<TextBlock Text="{Binding ItemText}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ButtonVm}">
<Button Command="{Binding ButtonCommand}">
<TextBlock Text="{Binding ButtonText}"/>
</Button>
</DataTemplate>
</ListView.Resources>
</ListView>
MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ListItemVm { ItemText = "something" } );
Items.Add(new ButtonVm { ButtonText = "click here" } );
}
private ObservableCollection<DependencyObject> _items = new ObservableCollection<DependencyObject>();
public ObservableCollection<DependencyObject> Items { get { return _items; } }
one viewModel for each type of item:
public class ListItemVm : DependencyObject
{
public string ItemText
{
get { return (string)GetValue(ItemTextProperty); }
set { SetValue(ItemTextProperty, value); }
}
public static readonly DependencyProperty ItemTextProperty =
DependencyProperty.Register("ItemText", typeof(string), typeof(ListItemVm), new UIPropertyMetadata(""));
}
public class ButtonVm : DependencyObject
{
public string ButtonText
{
get { return (string)GetValue(ButtonTextProperty); }
set { SetValue(ButtonTextProperty, value); }
}
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(ButtonVm), new UIPropertyMetadata(""));
public Command ButtonCommand
{
get { return (string)GetValue(ButtonCommandProperty); }
set { SetValue(ButtonCommandProperty, value); }
}
public static readonly DependencyProperty ButtonCommandProperty =
DependencyProperty.Register("ButtonCommand", typeof(Command), typeof(ButtonVm), new UIPropertyMetadata(""));
}
public class Command : ICommand { /* simple implementation of ICommand */ }
The UserControl below works well but I would like to make it easier to change the Style.
One thing I have tried is to convert this to a Custom Control, but I am stuck on basics like how to set the ToolTip inside the static method that deals with a change in a property (see below)
The other thing I tried to move the Style into a generic button style in the ResourceDictionary, but that is the subject of this question
How can I set the ToolTip in my subclassed Button?
Cheers
UserControl XAML:
<UserControl.Resources>
<ResourceDictionary Source="pack://application:,,,/Smack.Core.Presentation.Wpf;component/Themes/generic.xaml" />
</UserControl.Resources>
<Button x:Name="_button" Style="{StaticResource blueButtonStyle}" Command="{Binding AddNewItemCommand}" >
<StackPanel Orientation="Horizontal" >
<Image Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" Stretch="Uniform" VerticalAlignment="Center" />
<AccessText x:Name="_accesText" VerticalAlignment="Center">_Add New Subject</AccessText>
<ContentPresenter/>
</StackPanel>
</Button>
UserControl Code Behind:
public partial class AddNewItemButton : UserControl
{
public AddNewItemButton() { InitializeComponent(); }
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof (string), typeof (AddNewItemButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject { get { return (string) GetValue(SubjectProperty); } set { SetValue(SubjectProperty, value); } }
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
var control = obj as AddNewItemButton;
if (control == null) return;
control._accesText.Text = "_" + string.Format(MasterDetail.Subject_AddNew_Label, control.Subject.Capitalize());
control._button.ToolTip = string.Format(MasterDetail.Subject_AddNew_ToolTip, control.Subject.ToLower());
}
}
Failed attempt to create a Custom Control:
public class MyButton : Button
{
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"ItemName", typeof(string), typeof(MyButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var control = obj as MyButton;
if (control == null) return;
ToolTip = ??;
}
}
UPDATE
Based on Phil's answer, the control (the bottom one) is more 'lookless' than I'd like :--)
Result
Code
public class AddNewItemButton : Button
{
static AddNewItemButton() {
var type = typeof (AddNewItemButton);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
}
#region Subject
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof(string), typeof(AddNewItemButton),
new PropertyMetadata(default(string)));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
#endregion
}
Generic.xaml
<Style TargetType="{x:Type local:AddNewItemButton}">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject, Converter={StaticResource AddNewItemForToolTip}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AddNewItemButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Stretch="Uniform" VerticalAlignment="Center"
Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" />
<AccessText
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Subject, Converter={StaticResource AddNewItemForLabel}}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's an example of a custom button with a tooltip (based on the questions you've been asking recently):
This is the code
public class CustomButton : Button
{
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton),
new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public static readonly DependencyProperty SubjectProperty =
DependencyProperty.Register("Subject", typeof (string),
typeof (CustomButton), new PropertyMetadata(default(string)));
public string Subject
{
get { return (string) GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
}
This goes in Themes/generic.xaml
<System:String x:Key="Test">Add new: </System:String>
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Image here"
VerticalAlignment="Center" Padding="0,0,5,0"/>
<AccessText Grid.Column="1" VerticalAlignment="Center">
<AccessText.Text>
<MultiBinding StringFormat="{}_{0} {1}">
<Binding Source="{StaticResource Test}"/>
<Binding RelativeSource=
"{RelativeSource TemplatedParent}"
Path="Subject"/>
</MultiBinding>
</AccessText.Text>
</AccessText>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>