Change the value of a custom Dependency Property programmatically - c#

I want to change the value of a custom Dependency Property programmatically.
This is my XAML:
<Window.Resources>
<ResourceDictionary>
<Style
x:Key="TreeViewItemStyle"
TargetType="TreeViewItem">
<Style.Triggers>
<Trigger
Property="local:ColorHelper.IsColor"
Value="True" >
<Setter
Property="Foreground"
Value="{Binding Color}" />
</Trigger>
<Trigger
Property="local:ColorHelper.IsColor"
Value="False" >
<Setter
Property="Foreground"
Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
I want this style to be applied to a treeview
<TreeView
HorizontalAlignment="Stretch"
Margin="15,65,15,0"
x:Name="treeView1"
VerticalAlignment="Stretch"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
Grid.ColumnSpan="1"
Grid.RowSpan="2"
Grid.Column="1" />
And changing the color value of the IsColor property through a checkbox:
<CheckBox
Name="CHK_Gray"
VerticalAlignment="Center"
Foreground="DarkGray"
Grid.Row="6"
Grid.Column="0"
Grid.RowSpan="1"
Grid.ColumnSpan="2"
Unchecked="grayCheckBox_Unchecked"
Checked="grayCheckBox_Checked">
Show Created Assembly (in Grey)
</CheckBox>
The Dependency Property is created like this:
public class ColorHelper : DependencyObject
{
public static readonly DependencyProperty IsColorProperty = DependencyProperty.Register(
"IsColor", typeof(bool), typeof(ColorHelper), new PropertyMetadata(false));
public static void SetIsColor(DependencyObject target, Boolean value)
{
target.SetValue(IsColorProperty, value);
}
public static bool GetIsColor(DependencyObject target)
{
return (bool)target.GetValue(IsColorProperty);
}
}
How can i do in the Checked and Unchecked event to change the value of the IsColor property?
private void grayCheckBox_Checked(object sender, RoutedEventArgs e)
{
???
}
private void grayCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
???
}
Thank you very much for your help!

You need to get a reference to the element of which you want to set the attached property to begin with.
You could try this method to get the TreeViewItem elements of the TreeView and then set the property of all of them if that's that you want:
foreach(TreeViewItem tvi i tv.FindTreeViewItems())
ColorHelper.SetIsColor(tvi, true);
Note that you cannot edit the template programmatically. And even if you could, it wouldn't really help as it has already been applied to the actual elements.

Related

WPF Event Not Raised when Dependency Property Changed

I have the following custom control based on the "heavy option" at this link:
public partial class SelectableContentControl : ContentControl
{
public SelectableContentControl()
{
InitializeComponent();
var isCheckedDesc = DependencyPropertyDescriptor.FromProperty(IsCheckedProperty, typeof(SelectableContentControl));
isCheckedDesc.AddValueChanged(this, IsCheckedPropertyChanged);
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool),
typeof(SelectableContentControl), new PropertyMetadata(false));
private void IsCheckedPropertyChanged(object sender, EventArgs e)
{
var selectable = Content as IAmSelectable;
if (selectable != null) selectable.IsSelected = IsChecked;
}
}
The style defined for the SelectableContentControl is as follows:
<Style TargetType="{x:Type controls1:SelectableContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{TemplateBinding IsChecked}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and my usage:
<controls:SelectableContentControl Grid.Row="2" Content="{Binding Dummy}" IsChecked="{Binding Dummy.IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I want IsCheckedPropertyChanged to be called whenever the IsChecked value changes on the UI, but this isn't happening. Anyone see what I'm missing?
TemplateBinding works in a OneWay mode, meaning that the value is updated only in source-to-target direction (your control being the source, and the CheckBox inside the template the target). If you want the binding to work in TwoWay mode, you should use a normal Binding instead:
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
Note that you don't need to specify Mode=TwoWay on the binding, because CheckBox.IsChecked property binds in two-way mode by default.
See this question for more detailed info.

TwoWay binding of IsSelected property between ListBoxItem and ListBoxContainer causes unexpected behaviour. OneWayToSource works. Why?

Short version:
I am having a problem with the two-way binding of the IsSelected property of the ListBox container and the ListBox item, which causes unexpected behaviour in the appearance of datatemplated items, when changing their IsSelected property of items in my ViewModel. I am looking for help, since I don't understand what the problem is.
Long version:
I am creating a CustomControl using a ListBox. I am using a DataTemplate to style the objects in the ListBox.
DataTemplate:
<DataTemplate DataType="{x:Type substratePresenter:Target}">
<Ellipse Fill="{Binding MyColor}"
Width="{Binding Source={StaticResource WellSize}}"
Height="{Binding Source={StaticResource WellSize}}"
StrokeThickness="1.5"
Canvas.Left="{Binding Path=XPos}"
Canvas.Top="{Binding Path=YPos}"
ToolTip="{Binding Name}"
SnapsToDevicePixels="True"
Cursor="Hand">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding Path=MouseEnterCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding Path=MouseLeaveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Ellipse.Effect>
<!--THIS IS HACK SO THAT THE INITIAL STATE OF THE HOVEROVER SHADOW IS "OFF"-->
<DropShadowEffect Color="Blue" BlurRadius="10" ShadowDepth="0" Opacity="0" />
</Ellipse.Effect>
<Ellipse.Style>
<Style TargetType="Ellipse">
<Style.Resources>
<!-- REF for using Storyboard animation, Glowon: http://stackoverflow.com/questions/1425380/how-to-animate-opacity-of-a-dropshadoweffect -->
<Storyboard x:Key="GlowOn">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Effect).Opacity">
<SplineDoubleKeyFrame KeyTime="0:0:0.0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="GlowOff">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Effect).Opacity">
<SplineDoubleKeyFrame KeyTime="0:0:0.0" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="Blue" BlurRadius="10" ShadowDepth="0" Opacity=".75" />
</Setter.Value>
</Setter>
<Setter Property="Stroke" Value="Black"/>
<Style.Triggers>
<!--Handel target target selection-->
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Stroke" Value="White"/>
</DataTrigger>
<!--Handel target hovering-->
<!-- REF for using DataTrigger: https://msdn.microsoft.com/de-de/library/aa970679%28v=vs.90%29.aspx -->
<DataTrigger Binding="{Binding IsGroupHovered}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource GlowOn}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource GlowOff}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</DataTemplate>
As you can see above, I am using the IsSelected property to change the color of the stroke from black to white, when an item IsSelected is true. To select an item and correspondingly change its appearance I am binding the IsSelected property in the ItemContainerStyle to the IsSelected property of my datatemplated items.
ListBox XAML:
<ListBox
x:Name="TargetListBox"
BorderThickness="0"
Width="{StaticResource LayoutGridWidthColumn1}"
Height="{StaticResource LayoutGridHeightRow1}"
ItemsSource="{Binding Path=TargetCollection}"
SelectionMode="Extended"
Grid.Column="1" Grid.Row="1"
Background="Transparent"
>
<i:Interaction.Behaviors>
<behavior:RubberBandBehavior />
</i:Interaction.Behaviors>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" Background="Transparent"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDoubleClick" Handler="listBoxItem_DoubleClick" />
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Canvas.Left"
Value="{Binding XPos, Converter={StaticResource horizontalValueConverter},
ConverterParameter={StaticResource substrateWidth}}"/>
<Setter Property="Canvas.Top"
Value="{Binding YPos, Converter={StaticResource verticalValueConverter},
ConverterParameter={StaticResource substrateHeight}}"/>
<!--Bind IsSelected property of ListBoxItem to that of the Target-->
<!--REF: http://stackoverflow.com/questions/1875450/binding-the-isselected-property-of-listboxitem-to-a-property-on-the-object-from-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
<!--<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}"/>-->
<!--Hide the background-highlighting of the ListBox-Selection, since we handle this from the Items-->
<!--REF: http://stackoverflow.com/questions/2138200/change-background-color-for-selected-listbox-item-->
</Style>
</ListBox.ItemContainerStyle>
<!--REF: http://stackoverflow.com/questions/4343793/how-to-disable-highlighting-on-listbox-but-keep-selection-->
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
</ListBox.Resources>
</ListBox>
I am now trying to implement double-click behaviour to select groups of identical items. I have this double-click event method in my code behind:
void listBoxItem_DoubleClick(object sender, MouseButtonEventArgs e)
{
(((ListBoxItem)sender).Content as Target).MouseSelectGroupCommand.Execute(null);
}
The command MouseSelectGroupCommand of Target finds the other Targets of the group in the ObservableCollection TargetCollection, which are identical to the selected one and sets their IsSelected property to true.
The problem I am now having, is that when I perform a double-click on a target, only that target changes its stroke color, but not the other targets of the group.
To try and debug I have done the following:
1) Confirm that the IsSelected property of all targets in the group are indeed set to true, which is the case.
2) I have changed the binding from <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/> to <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}/>" in <ListBox.ItemContainerStyle>. When I do this, it works and the stroke color changes for the whole group as expected. However I lose the selection behaviour of the ListBox, which I would then have to reimplement (such as deselection, when selecting another item, etc.). I would therefore like to avoid this.
Furthermore I am using precisely the same method to change the DropShadowEffect of the whole group, when a member-target of that group is being hovered (see DataTemplate) and in that case it works perfectly fine.
I am therefore left to conclude, that it somehow has to do with the binding of the IsSelected property. I would appreciate any suggestions on how to resolve this.
Update:
Here is the code that is executed by the MouseSelectGroupCommand. It sends a Message using MvvmLight Messenger to its containing collection, which the finds other targets that are identical and sets their IsSelected property to true. I know it is not pretty at all, but I am still very new to WPF it is what I have got working ATM. I would love to hear suggestions on how to handle this better, although that would be different question altogether.
MouseSelectGroupCommand executed on double-click:
public RelayCommand MouseSelectGroupCommand { get; set; }
private void ExecuteSelectTargetGroup()
{
List<Target> selectedTarget = new List<Target>();
selectedTarget.Add(this);
Messenger.Default.Send(new SelectTargetGroup(selectedTarget));
}
SelectGroup command, executed in the ObservableCollection containing the targets, when receiving the SelectTargetGroup message:
public void SelectGroup(IList<Target> selectedTarget)
{
IList<Target> targetGroup = GetTargetsWithSameActions(selectedTarget[0]);
SetGroupSelected(targetGroup);
}
public void SetGroupSelected(IList<Target> targetGroup)
{
foreach (Target target in targetGroup)
{
target.PropertyChanged -= TargetPropertyChanged;
target.IsSelected = true;
target.PropertyChanged += TargetPropertyChanged;
}
}
And this is how I have the command set up in the constructor of the ObservableCollection:
Messenger.Default.Register<SelectTargetGroup>(this, msg => SelectGroup(msg.SelectedTargets));
Update: It has become clear to me that the root of the problem is in my sloppy implementation. The answer Василий Шапенко should help me achieve a much cleaner implementation and therefore work around the problem, which is why I accepted it.
Ok, here is a small solution:
First is background part. In the code below we create a main view model, add property Items to it, and fill it with bunch of models. OnModelSelectionChanged does the work by selecting model groups.
public class MainViewModel
{
private ObservableCollection<SelectionItemViewModel> items;
public MainViewModel()
{
FillItems();
}
private void FillItems()
{
var models=Enumerable.Range(0, 10)
.SelectMany(
index =>
Enumerable.Range(0, 3)
.Select(i => new Model() {Id = index, Name = string.Format("Name_{0}_{1}", index, i)})).Select(
delegate(Model m)
{
var selectionItemViewModel = new SelectionItemViewModel()
{
Value = m
};
selectionItemViewModel.PropertyChanged += OnModelSelectionChanged;
return selectionItemViewModel;
});
Items=new ObservableCollection<SelectionItemViewModel>(models);
}
private void OnModelSelectionChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
var model = sender as SelectionItemViewModel;
foreach (var m in Items.Where(i=>i.Value.Id==model.Value.Id && model!=i))
{
if (m.IsSelected != model.IsSelected)// This one to prevent infinite loop on selection, on double click there is no need for it
{
m.IsSelected = model.IsSelected;
}
}
}
}
public ObservableCollection<SelectionItemViewModel> Items
{
get { return items; }
set { items = value; }
}
}
public class SelectionItemViewModel:INotifyPropertyChanged
{
private bool isSelected;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value;
OnPropertyChanged();//For .Net<4.5, use OnPropertyChanged("IsSelected")
}
}
public Model Value { get; set; }
}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
XAML. Here is simple binding, nothing complex.
<Window x:Class="WpfApplication1.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">
<Grid>
<ListBox SelectionMode="Multiple" ItemsSource="{Binding Items}" DisplayMemberPath="Value.Name">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs - here we put our ViewModel into MainWindow DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
For double click support:
In MainWindow.xaml.cs:
private void Control_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var source = e.OriginalSource as FrameworkElement;
var mainViewModel = DataContext as MainViewModel;
if (source != null)
{
var model = source.DataContext as SelectionItemViewModel;
model.IsSelected = !model.IsSelected;
if (model != null)
{
foreach (var m in mainViewModel.Items.Where(i => i.Value.Id == model.Value.Id && model != i))
{
if (m.IsSelected != model.IsSelected)
{
m.IsSelected = model.IsSelected;
}
}
}
}
}
In MainWindow.xaml:
<ListBox MouseDoubleClick="Control_OnMouseDoubleClick" SelectionMode="Multiple" ItemsSource="{Binding Items}" DisplayMemberPath="Value.Name">
And comment the code inside OnModelSelection.
That is a direct rough approach. More elegant way is to create command binded to double click and attached to ListBoxItem, but this requires more code to write and understanding the concept of Attached Properties.
Also take a look at MouseButtonEventArgs, which will help you to determine which button is clicked, and which control key is pressed.
Key words for further readings: InputBinding,AttachedProperty,ICommand.

Button using dependency properties for a separate image per state

I have a Button control that I want to be able to reuse throughout project. Each time button enters a new state, a different image will be displayed. For now, I have Normal State and Pressed State.
Here's the XAML portion of the control:
<Button
x:Class="customImageButton.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<ContentControl Width="80">
<Grid>
<Image Name="Normal" Source="{Binding NormalState}"/>
<Image Name="Pressed" Source="{Binding PressedState}" Visibility="Hidden"/>
</Grid>
</ContentControl>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Normal" Property="Visibility" Value="Hidden"/>
<Setter TargetName="Pressed" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
Here's the code-behind for the control:
namespace customImageButton
{
public partial class ImageButton : Button
{
public ImageButton()
{
this.InitializeComponent();
}
public ImageSource NormalState
{
get { return base.GetValue(NormalStateProperty) as ImageSource; }
set { base.SetValue(NormalStateProperty, value); }
}
public static readonly DependencyProperty NormalStateProperty =
DependencyProperty.Register("NormalState", typeof(ImageSource), typeof(ImageButton));
public ImageSource PressedState
{
get { return base.GetValue(PressedStateProperty) as ImageSource; }
set { base.SetValue(PressedStateProperty, value); }
}
public static readonly DependencyProperty PressedStateProperty =
DependencyProperty.Register("PressedState", typeof(ImageSource), typeof(ImageButton));
}
}
...and here is its use:
<local:ImageButton Content="CustomButton" HorizontalAlignment="Left"
VerticalAlignment="Top" NormalState="Resources/Normal.png"
PressedState="Resources/Pressed.png"/>
My problem is that the images I've provided are not displaying. The Build Action for both images is Resource and I have tried using absolute path; however, that provided the same result. What am I missing?
Two problems with the code as listed:
The bindings need to be TemplateBinding.
The TargetType should refer to the "ImageButton" type, not Button.
Like this:
<ControlTemplate
xmlns:local="clr-namespace:customImageButton"
TargetType="{x:Type local:ImageButton}">
<Grid>
<ContentControl Width="80">
<Grid>
<Image Name="Normal" Source="{TemplateBinding NormalState}"/>
<Image Name="Pressed" Source="{TemplateBinding PressedState}" Visibility="Hidden"/>
</Grid>
</ContentControl>
</Grid>
Note: The above builds and runs, but Visual Studio complains:
'ImageButton' ControlTemplate TargetType does not match templated type 'Button'.
I suggest putting the control template in its own Style in the Themes/Generic.xaml resource dictionary, rather than inline.

Position a button after the last listviewitem

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 */ }

Setting focus in WPF with MVVM

I have Grid with multiple Textboxes. Depending on actions the user might take focus should be changed to one of the textboxes. My current solution uses a string property in the ViewModel and a data trigger in xaml to change focus. It works nicely but it seems a rather roundabout way to achieve this so I was wondering if it could be done in a clearner way?
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding FocusedItem}" Value="number">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=number}"/>
</DataTrigger>
<DataTrigger Binding="{Binding FocusedItem}" Value="name">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=name}"/>
</DataTrigger>
<DataTrigger Binding="{Binding FocusedItem}" Value="id">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=id}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
As you can see the value of the property and the name of the element is the same so I would like to do this i a single trigger instead of having one trigger per element.
Maybe someone can come up with a cleaner way?
Thanks in advance
The way I handled setting focus in one of my projects was by using a focus extension (I apologize I do not remember where I saw the original post this is from).
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
And then in xaml file I use it as a Dependency Property:
<TextBox Uid="TB1" FontSize="13" localExtensions:FocusExtension.IsFocused="{Binding Path=TB1Focus}" Height="24" HorizontalAlignment="Left" Margin="113,56,0,0" Name="TB_UserName" VerticalAlignment="Top" Width="165" Text="{Binding Path=TB1Value, UpdateSourceTrigger=PropertyChanged}" />
You can then use a binding to set the focus.

Categories