The below is my code for List box with button.
I have two issues here:
1) SelectionChanged not firing for me to get the selected item and its value.
2) My list box is for multiple items selection, so when i select one item the background is not set on the button.
How to solve these issues ?
<ListBox Name="listBox"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
SelectionChanged="TopicListboxSelectionChanged"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Name="AnswerCell"
Width="456"
Content="{Binding Path=Value}"
Background="#FFF2F4F7"
Foreground="Black"
Style="{StaticResource CellStyle}">
<Button.ContentTemplate>
<DataTemplate>
<TextBlock
Style="{StaticResource TextStyle}"
Padding="0,20,0,20"
TextAlignment="Center"
Text="{Binding}"/>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT
Here my text block with border
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="AnswerCellBack" Margin="0,0,0,4" Orientation="Horizontal">
<Border Name="borderColor" Background="#FFF2F4F7">
<TextBlock Name="Answertext"
Width="456"
Padding="10,20,10,20"
TextAlignment="Center"
Text="{Binding Path=AnswerValue}"
Style="{StaticResource AnswerTextStyle}"/>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Issues here:
1) How to change the selection item background color, i have set Border background in XAML.
2) How to add Multiple item selection.
I believe that your problem is your DataTemplate which consists of a button. A button will normaly handled the routed mouseclick event and will not give the Listbox an opertunity to act on it, therefore your not getting a selection event.
Try to change your button element to a border for example and see if your event is fireing then?
It might also not be a bad idea to use an attached property and bind the selection change to a command,
public class SelectionChangeCommand : DependencyObject
{
public static bool GetIsRegistered(DependencyObject obj)
{
return (bool)obj.GetValue(IsRegisteredProperty);
}
public static void SetIsRegistered(DependencyObject obj, bool value)
{
obj.SetValue(IsRegisteredProperty, value);
}
// Using a DependencyProperty as the backing store for IsRegistered. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsRegisteredProperty =
DependencyProperty.RegisterAttached("IsRegistered", typeof(bool), typeof(SelectionChangeCommand), new PropertyMetadata(false, new PropertyChangedCallback(RegisterForCommand)));
private static void RegisterForCommand(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Selector)
{
Selector sel = (Selector)d;
if ((bool)e.NewValue)
{
sel.SelectionChanged += sel_SelectionChanged;
}
else
{
sel.SelectionChanged -= sel_SelectionChanged;
}
}
}
static void sel_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is Selector)
{
Selector sel = (Selector)sender;
ICommand command = GetCommand(sel);
if (command!=null && command.CanExecute(null))
command.Execute(sel);
}
}
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(SelectionChangeCommand), new PropertyMetadata(null));
}
after you reference the xmlns you could use the code in xaml like so:
<ListBox kernelAttached:SelectionChangeCommand.Command="{Binding SelectedLinkCommand}"
kernelAttached:SelectionChangeCommand.IsRegistered="True" >
Related
I've been practicing MVVM pattern and come across the problem which I don't know how to solve. The problem is pretty simple and I hope the solution as well. The point is that I'm trying to use a command and binding for an element, when I'm setting up it's style, but I can't do it at the same time.
I have the following style for ListBoxItem:
<Style x:Key="OptionDieStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Width="Auto"
BorderThickness="1.5"
CornerRadius="10"
Height="30"
Background="Transparent"
Margin="5">
<TextBlock Margin="5"
Text="{Binding}"
Foreground="White"
VerticalAlignment="Center"/>
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="#Omitted"
</Border.InputBindings>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This ListBox is filled with strings which are displayed in particular way because of the style.
That means that when I want to handle user's click on that element, using command, I need to set DataContext, which contains ViewModel, where command is located, for this item, but if I do it no content will be displayed in ListBox Items. Certainly, I could set event for this Border like "MouseDown" but it would be the wrong way to use MVVM.
If you have some thoughts how to solve this using commands please share them.
To make these scenarios easier, I've derived a class from CommandBindin. In which he added the ability to bind to ViewModel commands. You can set the binding to both Execute and PreviewExecute.
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace CommonCore.AttachedProperties
{
public class CommandBindingHelper : CommandBinding
{
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty PreviewCommandProperty =
DependencyProperty.RegisterAttached(
"PreviewCommand",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
public BindingBase Binding { get; set; }
public BindingBase PreviewBinding { get; set; }
public CommandBindingHelper()
{
Executed += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, CommandProperty, Binding);
CanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, CommandProperty, Binding);
PreviewExecuted += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
PreviewCanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
}
private static void PrivateExecuted(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
if (command is not null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
private static bool PrivateCanExecute(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
return command?.CanExecute(parameter) ?? true;
}
private static UIElement CheckSender(object sender)
{
if (sender is not UIElement element)
throw new NotImplementedException("Implemented only for UIElement.");
return element;
}
private static ICommand GetCommand(UIElement sender, DependencyProperty commandProp, BindingBase commandBinding)
{
BindingBase binding = BindingOperations.GetBindingBase(sender, commandProp);
if (binding != commandBinding)
{
if (commandBinding is null)
{
BindingOperations.ClearBinding(sender, commandProp);
}
else
{
BindingOperations.SetBinding(sender, commandProp, commandBinding);
}
}
return (ICommand)sender.GetValue(CommandProperty);
}
}
}
An example of its use:
using Simplified; // This is the space of my ViewModelBase implementation
using System.Collections.ObjectModel;
namespace Core2023.SO.ASTERY.CommandInListItem
{
public class ListItemsViewModel : ViewModelBase
{
public ObservableCollection<string> Items { get; } = new("first second third fourth fifth".Split());
public RelayCommand RemoveCommand => GetCommand<string>(item => Items.Remove(item));
}
}
<Window x:Class="Core2023.SO.ASTERY.CommandInListItem.ListItemsWindow"
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:Core2023.SO.ASTERY.CommandInListItem"
xmlns:ap="clr-namespace:CommonCore.AttachedProperties;assembly=CommonCore"
mc:Ignorable="d"
Title="ListItemsWindow" Height="450" Width="800"
FontSize="20">
<Window.DataContext>
<local:ListItemsViewModel/>
</Window.DataContext>
<Window.CommandBindings>
<ap:CommandBindingHelper Command="Delete" Binding="{Binding RemoveCommand}"/>
</Window.CommandBindings>
<Grid>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" Margin="5">
<TextBlock Text="{Binding}"/>
<Button Content="Remove"
Command="Delete"
CommandParameter="{Binding}"/>
</UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
What do i do if I want to set different name?
The easiest way is to create a command in Window or (better) App resources.
<Application.Resources>
<RoutedUICommand x:Key="commands.Remove" Text="Delete Item" />
</Application.Resources>
<Button Content="Remove"
Command="{StaticResource commands.Remove}"
CommandParameter="{Binding}"/>
Create a static property containing the command. But it should be created at the View level, not the ViewModel.
public static class MyCommands
{
public static RoutedUICommand Remove { get; }
= new RoutedUICommand("Delete Item", "Remove", typeof(MyCommands));
public static RoutedUICommand Add { get; }
= new RoutedUICommand("Add Item", "Add", typeof(MyCommands));
}
<Button Content="Remove"
Command="{x:Static local:MyCommands.Remove}"
CommandParameter="{Binding}"/>
Adding a markup extension to the previous version to make it easier to use in XAML.
public class MyCommandsExtension : MarkupExtension
{
public string? CommandName { get; set; }
public MyCommandsExtension() { }
public MyCommandsExtension(string commandName) => CommandName = commandName;
public override object ProvideValue(IServiceProvider serviceProvider)
=> CommandName switch
{
nameof(MyCommands.Remove) => MyCommands.Remove,
nameof(MyCommands.Add) => MyCommands.Add,
_ => throw new NotImplementedException()
};
}
<Button Content="Remove"
Command="{local:MyCommands Remove}"
CommandParameter="{Binding}"/>
The approach above is working fine, but only if we're going to use commands with default ApplicationCommands' names and won't give them individual names. I was racking my brains and eventually found the proper approach.
All I had to do is just make my command static in ViewModel and change definition for my command in XAML like this:
Command="{x:Static viewModels:MyViewModel.MyCommand}
I have a usercontrol which is a dropdown displaying checkboxes. There is a checkbox click event that calls the SetText function, which sets the text based on what has been selected (which I want to keep). I would also like to add a function to the usercontrol maybe thru a command which sets a custom function. For example, when they select a checkbox I can call the function set in the viewmodel, as well as keep the SetText function.
I tried adding a Command to the checkbox. As well as a dependency property to the usecontrol for the Command. In addition a simple function to use in the viewmodel
-UserControl.xaml
<ComboBox
x:Name="CheckableCombo"
SnapsToDevicePixels="True"
OverridesDefaultStyle="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"
IsSynchronizedWithCurrentItem="True"
MinWidth="120"
MinHeight="20"
ItemsSource="{Binding ElementName=UserControl, Path=ItemsSource}"
DataContext="{Binding ElementName=UserControl, Path=DataContext}"
>
<ComboBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
Click="CheckBox_Click"
Command="{Binding YourCommand}"
/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding YourCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
-UserControl.xaml.cs
public ICommand YourCommand
{
get { return (ICommand)GetValue(YourCommandProperty); }
set { SetValue(YourCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for YourCommand. This enables animation, styling, binding, etc...
//public static readonly DependencyProperty YourCommandProperty =
// DependencyProperty.Register("YourCommand", typeof(ICommand), typeof(ComboWithCheckboxes), new PropertyMetadata(0));
public static readonly DependencyProperty YourCommandProperty =
DependencyProperty.Register("YourCommand", typeof(ICommand), typeof(ComboWithCheckboxes));
#endregion
public ComboWithCheckboxes()
{
InitializeComponent();
}
/// <summary>
///Whenever a CheckBox is checked, change the text displayed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
SetText();
}
/// <summary>
///Set the text property of this control (bound to the ContentPresenter of the ComboBox)
/// </summary>
private void SetText()
{
this.Text = (this.ItemsSource != null) ?
this.ItemsSource.ToString() : this.DefaultText;
// set DefaultText if nothing else selected
if (string.IsNullOrEmpty(this.Text))
{
this.Text = this.DefaultText;
}
}
}
-ViewModel.cs
public ViewModel()
{
ViewModelCommand = new DelegateCommand(MethodTest, canExecuteTest);
itemSource = new ObservableNodeList();
Node a = new Node("English");
a.IsSelected = true;
itemSource.Add(a);
Node b = new Node("Hebrew");
b.IsSelected = false;
itemSource.Add(b);
Node c = new Node("Swedish");
c.IsSelected = false;
itemSource.Add(c);
Node d = new Node("German");
d.IsSelected = false;
itemSource.Add(d);
}
private bool canExecuteTest(object obj)
{
return true;
}
private void MethodTest(object obj)
{
System.Windows.MessageBox.Show("Test Method");
}
My expected result was to be able to hit the command function when either select or deselecteding the checkboxes
I simplify your user control to be more readable and focus only on working external-command. I Modify the Binding of Command. In the list you got local data-context of item, but it is needed to bind command to external-data-context.
<ComboBox ItemsSource="{Binding ElementName=UserControl, Path=ItemsSource}">
<ComboBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding .}"
Click="CheckBox_Click"
Command="{Binding ElementName=UserControl,Path=YourCommand}">
</CheckBox>
</HierarchicalDataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In the UserControl1.cs I got:
public ICommand YourCommand
{
get { return (ICommand)GetValue(YourCommandProperty); }
set { SetValue(YourCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for YourCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty YourCommandProperty =
DependencyProperty.Register("YourCommand", typeof(ICommand), typeof(UserControl1), new PropertyMetadata(null));
I tested and it works for me.
I have tried to google this but I am not super clear on the suggestions I see people making.
I have 3 buttons within a user control that are exactly the same except for automation id and text. Instead of duplicating this code across all buttons, I would like to create a common control or control template that I can use. My problem is these buttons have nested controls whose text I need to change but I'm not sure how to do this.
<UserControl>
<Grid>
<StackPanel x:Name="myStackPanel" Grid.Row="1" Padding="0">
<Button AutomationProperties.AutomationId="CallButton" x:Name="CallButton" Style="{StaticResource ButtonStyle}">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="CallIcon" x:Name="CallIcon" Symbol="Phone" Style="{StaticResource SymbolStyle}" />
<StackPanel>
<TextBlock AutomationProperties.AutomationId="CallLabel" Text="Call" Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="CallText" Text="123-456-7890" Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
<Button AutomationProperties.AutomationId="EmailButton" x:Name="EmailButton" Style="{StaticResource ButtonStyle}">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="EmailIcon" x:Name="EmailIcon" Symbol="Mail" Style="{StaticResource SymbolStyle}"/>
<StackPanel >
<TextBlock AutomationProperties.AutomationId="EmailLabel" Text="Email" Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="EmailText" Text="meetme#email.com" Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
<Button AutomationProperties.AutomationId="WebsiteButton" x:Name="WebsiteButton" Style="{StaticResource ButtonStyle}">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="WebsiteIcon" x:Name="WebsiteIcon" Symbol="Link" Style="{StaticResource SymbolStyle}"/>
<StackPanel >
<TextBlock AutomationProperties.AutomationId="WebsiteLabel" Text="Website" Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="WebsiteText" Text="http://meetme.com" Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
</StackPanel>
</Grid>
</Grid>
As you can see, the code for all 3 buttons is same. All I want to do is create a control where I can set the automation ids and text properties of the nested controls.
Thanks!
You can create a button based on a UserControl (add a new UserControl). It will allow you to enjoy all the default button's properties, events and states (OnClick, Command, etc.) and to add your own properties, template and behavior.
Using dependency properties instead of simple properties is strongly advised if you want to use bindings or animations on them.
C#:
public partial class CustomButton : Button
{
#region IconAutomationId
public string IconAutomationId
{
get { return (string)GetValue(IconAutomationIdProperty); }
set { SetValue(IconAutomationIdProperty, value); }
}
public static readonly DependencyProperty IconAutomationIdProperty =
DependencyProperty.Register("IconAutomationId", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region LabelAutomationId
public string LabelAutomationId
{
get { return (string)GetValue(LabelAutomationIdProperty); }
set { SetValue(LabelAutomationIdProperty, value); }
}
public static readonly DependencyProperty LabelAutomationIdProperty =
DependencyProperty.Register("LabelAutomationId", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region TextAutomationId
public string TextAutomationId
{
get { return (string)GetValue(TextAutomationIdProperty); }
set { SetValue(TextAutomationIdProperty, value); }
}
public static readonly DependencyProperty TextAutomationIdProperty =
DependencyProperty.Register("TextAutomationId", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region Symbol
public object Symbol
{
get { return (object)GetValue(SymbolProperty); }
set { SetValue(SymbolProperty, value); }
}
public static readonly DependencyProperty SymbolProperty =
DependencyProperty.Register("Symbol", typeof(object), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region Label
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region Text
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
public CustomButton()
{
InitializeComponent();
}
}
XAML:
<Button x:Class="WpfApplication1.CustomButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="customButton">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="{Binding IconAutomationId, ElementName=customButton}"
Symbol="{Binding Symbol, ElementName=customButton}"
Style="{StaticResource SymbolStyle}"/>
<StackPanel >
<TextBlock AutomationProperties.AutomationId="{Binding LabelAutomationId, ElementName=customButton}"
Text="{Binding Label, ElementName=customButton}"
Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="{Binding TextAutomationId, ElementName=customButton}"
Text="{Binding Text, ElementName=customButton}"
Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
Use:
<local:CustomButton AutomationProperties.AutomationId="CallButton"
x:Name="CallButton"
Style="{StaticResource ButtonStyle}"
IconAutomationId="CallIcon"
LabelAutomationId="CallLabel"
TextAutomationId="CallText"
Symbol="Phone"
Label="Call"
Text="123-456-7890"/>
It may be more work than you feel like doing but it sounds like you're going to want to create a UserControl. It should inherit from Button in the code-behind:
public partial class MyButton: Button
In the XAML you will include essentially the guts of what you have in each of the buttons now.
The tedious part is that you will then (in the code-behind) need to create a DependencyProperty for each of the "properties" you will want to set in this control: for example, one for the CallIconAutomationId, one for the CallLabelAutomationId, one for the CallLabelText, etc. You will then bind each of these properties in the XAML to the dependency property. These properties become the data that you will set on each individual MyButton (your new UserControl).
Then, in the container that is hosting these controls (which appears to be another UserControl in your example above) you would set these custom properties on each of your new MyButton controls, which will look something like this:
<myNamespace:MyButton EmailIconAutomationId="EmailIcon" LabelAutomationId="EmailLabel" />
etc.
Basically, you're creating a new control (a UserControl) based on the Button control (which gives you most of your functionality) and adding new custom properties directly to that new control (which work just like all the other control properties you're accustomed to).
I have custom control MyGrid
public class MyGrid : Canvas
{
//...
ObservableCollection<object> items = new ObservableCollection<object>();
public ObservableCollection<object> Items
{
get { return items; }
set {
items = value;
UpdateValues();
UpdateGrid();
}
}
//..
}
And I want Items to be bindable from XAML code:
<local:MyGrid Items="{Binding Numbers}" />
Where Numbers is ObservableCollection (which works fine, I can use it to bind to default controls).
I've tried to define Items as DependencyProperty, but it is static and I need to use more than one control on page with different sources of data, so using static items won't work. The code above doesn't work as well. InitializeComponent() throws an exception: Failed to assign to property 'App.MyGrid.Items'. [Line: 27 Position: 114]. How can I make it work?
As your MyGrid extends from Canvas (Which is long last also a DependecyObject) you could implement the Dependency property inside the MyGrid.
You could then also implement it with a PropertyChangedCallback which would allow you to register/unregister to the event itself, where you could then update your grid / values
So you could change the MyGrid like this:
public class MyGrid : Canvas
{
protected static PropertyChangedCallback ItemsPropertyChangedCallback = new PropertyChangedCallback(ItemsPropertyChanged);
public static DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached("Items", typeof(INotifyCollectionChanged), typeof(MyGrid), new PropertyMetadata(null, ItemsPropertyChangedCallback));
private static void ItemsPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyGrid thisGrid = (MyGrid)sender;
if (thisGrid == null)
{
return;
}
thisGrid.UnregisterItems(e.OldValue as INotifyCollectionChanged);
thisGrid.RegisterItems(e.NewValue as INotifyCollectionChanged);
thisGrid.Refresh();
}
public INotifyCollectionChanged Items
{
get
{
return (INotifyCollectionChanged)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
protected void UnregisterItems(INotifyCollectionChanged items)
{
if (items == null)
{
return;
}
items.CollectionChanged -= ItemsChanged;
}
protected void RegisterItems(INotifyCollectionChanged items)
{
if (items == null)
{
return;
}
items.CollectionChanged += ItemsChanged;
}
protected virtual void UpdateValues()
{
System.Diagnostics.Debug.WriteLine("Updating values");
}
protected virtual void UpdateGrid()
{
System.Diagnostics.Debug.WriteLine("Updating grid");
}
public void Refresh()
{
UpdateValues();
UpdateGrid();
}
protected virtual void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Refresh();
}
public MyGrid()
{
}
}
and in Xaml you could then later on Bind to the Items property. When the Items property is changed with another collection, it will unregister from the changed event of the last object (if there was one), and then register to the new object (if there is one). Afterwards, it will call the Refresh method of your class (that then calls the UpdateValues / UpdateGrid methods)
I also partly agree with #user3248647 that you should take advantage of Binding and ContentTemplates when you can, but if you cannot use that, you could get your DependencyProperty reacting at least like this.
And yes, the DependencyProperty is static on the class, but the property itself is always implemented inside the class. When using the PropertyChangedCallback, just cast the sender back to "MyGrid" and then you can change the instance members :)
May be this will help you.
<ItemsControl ItemsSource="{Binding items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,5,5,0" VerticalAlignment="Top">
<TextBlock TextAlignment="Right" FontWeight="Bold" Text="{Binding yourVariable}" Height="16"/>
<TextBlock TextAlignment="Right" Text="{Binding yourVariable1}" FontSize="26"/>
<TextBlock TextAlignment="Right" Text="{Binding yourVariable2}" FontSize="10" Foreground="DarkGray"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer Padding="{TemplateBinding Padding}" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
This may be not actually what you looking for. But I think this can help you.
Hello I have a dataGrid as follows
<sdk:DataGrid IsReadOnly="True"
ItemsSource="{Binding NodeCollection}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectionMode="Single"
AutoGenerateColumns="False">
Previously I had another DataGrid which was populated with the content of the SelectedItem from the above datagrid. Now I had to introduce a customized tab control which can add new tabs dynamically. The tab item must now display the content of the SelectedItem. I have a button(Add New) which adds a new tab item (duplicates the previous tab item) to the tab control.
<Button Content="Add New" Command="{Binding AddNewTemplateCommand}"/>
<templateTabs:BindableTabControl MyItemsSource="{Binding ModalityTemplates}">
<templateTabs:BindableTabControl.TabItemTemplate>
<DataTemplate>
<tabView:ModalityTemplateView/>
</DataTemplate>
<templateTabs:BindableTabControl.TabHeaderItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="2,2,2,2">
<TextBlock Text="{Binding Header, Mode=TwoWay}" />
<Button Height="16" Width="16" Margin="5,0,0,0" Padding="0" Command="{Binding DeleteTemplateCommand}" Content ="X" />
</StackPanel>
</DataTemplate>
</templateTabs:BindableTabControl.TabHeaderItemTemplate>
</templateTabs:BindableTabControl>
Currently I need to selected the item from the dataGrid and click the add new button to display the first default tab item. However I want that the first tab item be displayed as soon as an item from the dataGrid is selected.
Looking for a simple workaround.
Since the BindableTabControl is your code you can easily add a DependencyProperty to it that serves as a bindable source for the first (and only) item:
<sdk:DataGrid x:Name="NodeSelector" ... />
<templateTabs:BindableTabControl
SingletonItem="{Binding Path=SelectedItem, ElementName=NodeSelector}"
MyItemsSource="{Binding Path=ModalityTemplates}">
...
</templateTabs:BindableTabControl>
Your tab control needs to react to any change of this new property:
public object SingletonItem
{
get { return (object) GetValue(SingletonItemProperty); }
set { SetValue(SingletonItemProperty, value); }
}
public static readonly DependencyProperty SingletonItemProperty =
DependencyProperty.Register("SingletonItem", typeof (object), typeof (BindableTabControl),
new PropertyMetadata(OnSingletonItemChanged));
public static void OnSingletonItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((BindableTabControl) d).UpdateItems();
}
private void UpdateItems()
{
//it is up to you to implement this method, so this is only a sketch
//the null check for SingletonItem is missing too
if (IsNullOrEmpty(MyItemsSource))
{
Items = Enumerable.FromSingleItem(SingletonItem);
} else {
Items = MyItemsSource;
}
}
...
public static void OnMyItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ( e.OldValue != null ) { RemoveItemsChangedEventHandlers( e.OldValue as INotifyCollectionChanged ); }
if ( MyItemsSource != null ) { AddItemsChangedEventHandlers( MyItemsSource as INotifyCollectionChanged ); }
((BindableTabControl) d).UpdateItems();
}
private void MyItemsSourceItemsChanged(...)
{
((BindableTabControl) d).UpdateItems();
}