Binding Command to ItemsControl Item - c#

I am working on a Win10 UWP app using MVVMLight (I've never used MVVMLight, and never done commands "properly"). I have an ItemsControl bound to an ObservableCollection. The Participant has two properties - Name and Laps. I have controls in the ItemsControl.ItemTemplate to display a button (subtract), the Name property of the item, the Laps property of the item, and another button (add). Here is the XAML:
<ItemsControl
ItemsSource="{Binding Participants, Mode= TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid
MinWidth="300"
Margin="0, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="2*"></ColumnDefinition>
<ColumnDefinition
Width="6*"></ColumnDefinition>
<ColumnDefinition
Width="2*"></ColumnDefinition>
<ColumnDefinition
Width="3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button
Content=""
FontFamily="Segoe MDL2 Assets"
FontSize="20"
Grid.Column="0"
Margin="0, 0, 12, 0"></Button>
<TextBlock
Text="{Binding Path=Name, Mode=TwoWay}"
FontSize="20"
FontWeight="Bold"
Grid.Column="1"></TextBlock>
<TextBlock
Text="{Binding Laps, Mode=TwoWay}"
FontSize="20"
FontWeight="Bold"
Grid.Column="2"></TextBlock>
<Button
Content=""
FontFamily="Segoe MDL2 Assets"
FontSize="20"
Command="{Binding AddLapCommand}"
CommandParameter="{Binding}"
Grid.Column="3"
Margin="12, 0, 0, 0"></Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The view model creates an ObservableCollection called Participants. I am trying to bind the add button (and then of course the subtract button) to a RelayCommand in the VM. I've tried a number of things, so I can't post all I've tried here. Here is the latest of what I have, (but it still doesn't work):
public RelayCommand<object> AddLapCommand
{
get
{
if (_addLapCommand == null)
{
_addLapCommand = new RelayCommand<object>((e) => ExecuteAddLapCommand(e));
}
return _addLapCommand;
}
}
private void ExecuteAddLapCommand(object o)
{
throw new NotImplementedException();
}
The intellisense in the xaml tells me that it cannot resolve the property AddLapCommand in data context of type LapCounter.Model.Participant. I'm not trying to get to the Participant class, but to the HomeViewModel class, which is the DataContext of the page. I think I can see where it is getting the LapCounter.Model.Participant from, from the individual item in the collection that the ItemsControl is bound to. But I was under the impression that if a DataContext couldn't be found, it would continue up the visual tree until it found the right one. Here's the DataContext in the page declaration:
DataContext="{Binding Home, Source={StaticResource Locator}}"
How do I get it to look at the VM for the RelayCommand? What I need to do is have the button send the Participant that it represents to the RelayCommand as a parameter, and use that to increment (and decrement in the case of the subtract button) the Laps integer for that particular Participant.
I appreciate any help I can get. Thanks!
EDIT
Added Participants property from the VM to show because my view is not updating. Here is the Participants property:
/// <summary>
/// The <see cref="Participants" /> property's name.
/// </summary>
public const string ParticipantsPropertyName = "Participants";
private ObservableCollection<Participant> _participants = new ObservableCollection<Participant>();
/// <summary>
/// Sets and gets the Participants property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ObservableCollection<Participant> Participants
{
get
{
return _participants;
}
set
{
if (_participants == value)
{
return;
}
_participants = value;
RaisePropertyChanged(() => Participants);
}
}
Thanks to Eldar Dordzhiev for his help so far!

Try this:
Command="{Binding Home.AddLapCommand, Source={StaticResource Locator}}"
There's not much to explain to you as long as everything you've written is absolutely right.
As for incrementing\decrementing the Laps, you can simply pass the Participant into the command handler and alter the Laps. If you use MVVMLight that is done with RelayCommand<Participant>. Don't forget to pass the Participant in CommandParameter.

Related

UWP update a itemscontrol with an array that keeps updating

I have an array that keeps changing its values, because of this I want to have the apps UI refreshing every time the array's values do. I have this bound with an itemsControl. I can show the first array's values but then I can't update them I have tried .items.Clear() but its not working. Here are snippets of the .xaml and the xaml.cs. I actually took the code of the .xaml from a question from this site.
.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Text="Testing" IsReadOnly="True"></TextBox>
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding itemsControl}"
FontSize="24">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto"
Margin="0 12"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk0" Text="{Binding}" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
.xaml.cs
String c = (new String(cArray));
string[] arr = null;
string[] data = null;
if (c != null)
{
arr = c.Split('\n');
if (arr.Length > 0)
{
data = arr[0].Split(',');
}
}
for(int index = 0; index < 4; index++)
{
itemsControl.Items.Add(float.Parse(data[index]));
}
itemsControl.Clear();
If anyone has an idea of how I can do this I will be very grateful, thanks in advance and I will try to answer any questions as soon as possible!
What you're missing is an understanding of how bindings are triggered to update.
The INotifyPropertyChanged interface contains a method (PropertyChanged) and when called and passed the name of a property will tell the binding system that the property has changed and the binding should be updated.
INotifyCollectionChanged is the equivalent for collections, and communicates when a collection has changed. i.e. something added, removed, or the list cleared.
ObservableCollection<T> contains an implementation of INotifyCollectionChanged that makes it easy to work with lists, collections, etc. that change.
If you used an ObservableCollection<float> instead of an array you'd be able to modify the list and have the UI updated to reflect this easily.
As a starter, see the following which demonstrates how easy it is to use an ObservableCollection.
XAML:
<StackPanel>
<Button Click="Button_Click">add an item</Button>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
code behind;
public MainPage()
{
this.InitializeComponent();
// Initialize the property
this.Items = new ObservableCollection<string>();
// Use self as datacontext (but would normally use a separate viewmodel)
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// add a new item to the UI
this.Items.Add(DateTime.Now.ToString());
}
// The "collection" that is shown in the UI
public ObservableCollection<string> Items { get; set; }

Pass update parameter in WPF using MVVM

I am having a problem updating Entity while using WPF MVVM and commands.
My WPF looks like:
<Popup Margin="10,10,0,13" Name="UpdatePopup" HorizontalAlignment="Left" VerticalAlignment="Top" Width="450" Height="100" IsOpen="{Binding IsOpen, Mode=TwoWay}">
<Border Padding="5" Background="WhiteSmoke">
<StackPanel Orientation="Horizontal" DataContext="{Binding CommendationEntity}" Width="450" Height="100">
<Label Content="Nazwa żódła" Margin="10,10,10,10" Width="75" Height="30"/>
<TextBox Text="{Binding Name}" Margin="10,10,10,10" Width="130" Height="30" x:Name="Name" />
<Button Command="{Binding DataContext.UpdateCommand, ElementName=UpdatePopup}" CommandParameter="{Binding id}" Content="Update" Margin="10,10,10,10" Width="80" Height="30"/>
<Button Command="{Binding DataContext.CancelCommand, ElementName=UpdatePopup}" Content="Anuluj" Margin="10,10,10,10" Width="80" Height="30"/>
</StackPanel>
</Border>
</Popup>
Now to update record I need id and new name so I am passing id with button binding, and I am having trouble passing name, my update method looks like:
public void UpdateEntity(object obj)
{
this.CommendationEntity = this._catalog.Commendations.Single(entity => entity.id == (int)obj);
this.CommendationEntity.Name = this.Name;
this._catalog.Commendations.Attach(this.CommendationEntity);
this._catalog.Entry(this.CommendationEntity).State = System.Data.Entity.EntityState.Modified;
this._catalog.SaveChanges();
}
And in the view model I have a property named:
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
this._name = value;
RaisePropertyChanged("Name");
}
}
But when I click Update id is passed as (object obj), with is right, but Name property does not update, what could be wrong?
Maybe its data context (DataContext="{Binding CommendationEntity}") as model name and view model property has same name? I'm new WPF so I can be wrong.
Or maybe there is a way to just click button and whole object will be passed as (object obj)?
this.Name indicates that the Name property belongs to the same class as the UpdateCommand property and then the path of the binding to the Name property should be set up the same way:
<TextBox Text="{Binding DataContext.Name, ElementName=UpdatePopup}" Margin="10,10,10,10" Width="130" Height="30" x:Name="Name" />
Otherwise you are binding to some other Name property or the binding simply fails.
Two possibilities:
Is the binding for Name correct? For the commands you are using DataContext.UpdateCommand, ElementName=UpdatePopup so it may be you need to do this as well. If the binding fails you don't get an exception but you should get a message in the output window of VS - worth checking there.
If for some reason in the Name setter: if (_name == value) (perhaps both are emptry strings?) is true then the name won't update.
As per you code you are trying to bind Name property of CommendationEntity with textbox control.It is not getting updated because you have not implemented NotifyPropertyChanged for this.CommendationEntity.Name property. You need to implement INotifyProperty at CommendationEntity to reflect the property changed of CommendationEntity class.

Can I swap a buttons content databind to a different databind in code?

I cannot find any examples to make me understand how and if I can change the databind in c# at the click of a button on, in my case a toggleswitch, Basically I have 32 buttons in my app and those 32 buttons act the same but need different text with-in them depending on some toggle switches they are currently databinded so the text can be saved and retrieved from local storage but what values it gets depends on the state of these toggle switches.
So I currently have :
<Button x:Name="_ovButton1" Content="{Binding Source={StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}" Margin="2,0,250,0" VerticalAlignment="Top" FontSize="14" Height="72" FontWeight="Bold" MouseLeftButtonUp="_ovButton1_MouseLeftButtonUp" MouseLeftButtonDown="_ovButton1_MouseLeftButtonDown" ClickMode="Hover" Hold="_ovButton1_Hold"/>
and I want when a user changes the state of a toggleswitch to change the
{StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}
to for example:
{StaticResource AppSettings}, Path=ovName1_2Value, Mode=TwoWay}
but I cannot find any example that shows how to do that in c#
what code do I need to do that?
You can specify the target of databinding in code like this:
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);
See more at http://msdn.microsoft.com/en-us/library/ms742863.aspx
-- Edit Note I don't have access to a WP8 Emulator to test this ---
In the view model it looks like this:
public List<string> Members
{
get { return _Members; }
set { _Members = value; OnPropertyChanged(); }
}
public MainVM()
{
// Simulate Asychronous access, such as to a db.
Task.Run(() =>
{
Thread.Sleep(2000);
Members = new List<string>() {"Alpha", "Beta", "Gamma", "Omega"};
});
}
The code behind on the main page sets the datacontext (shared with all the child controls) as such:
public MainWindow()
{
InitializeComponent();
// Set the windows data context so all controls can have it.
DataContext = new MainVM();
}
The Mainpage Xaml to bind to members is like this
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[0] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[1] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[2] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[3] }" />
The result is this visually:
I based this on my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding for more info and a fuller example.
I think your best bet is going to be to use a collection of strings and bind to that collection. You can either change the collection when a toggle is switched, or keep 6 collections and bind to the collection that is for the toggle.
Xaml:
<ItemsControl x:Name="Buttons" ItemsSource="{Binding ButtonTextCollection}">
<ItemsControl.ItemsPanel>
<toolkit:WrapPanel/>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="100" Height="70" Content="{Binding}" Click="OnButtonClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your code-behind would have the event handler for your button click
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var text = ((Button) sender).Content.ToString();
// Send the text
}
Your ViewModel would hold the ButtonTextCollection property and would change based on the toggle.
public ICollection<string> ButtonTextCollection
{
get { return _buttonTextCollection; }
set
{
_buttonTextCollection = value;
OnPropertyChanged("ButtonTextCollection");
}
}
When you want to change the text, you would change the ButtonTextCollection
public void ChangeButtonText()
{
ButtonTextCollection = new Collection<string> {"A", "B",...};
}

WPF UserControl custom paste command across multiple child controls with MVVM

Consider the following UserControl:
<UserControl ...>
<UserControl.Resources>
<inf:DecimalToPercentageConverter x:Key="DecimalToPercentageConverter"/>
</UserControl.Resources>
<Expander Margin="0,5,5,5" Style="{DynamicResource ExpanderStyle}" IsExpanded="True" IsTabStop="False">
<Expander.Header>
<TextBlock FontSize="15" Foreground="White" Text="{x:Static resources:Strings.GeneralOptions}"/>
</Expander.Header>
<ItemsControl ItemsSource="{Binding DaysViewSource}" IsTabStop="False" Name="ItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="White" Padding="5" BorderThickness="2">
<HeaderedContentControl IsTabStop="False" Width="240">
<HeaderedContentControl.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Date, StringFormat=d}" FontSize="14" Margin="3"/>
<TextBlock Text="{Binding Date, StringFormat=(ddd)}" FontSize="14" Margin="3"/>
</StackPanel>
</HeaderedContentControl.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
<Image Margin="3" Width="16" Height="16" Source="{DynamicResource InformationIcon}" />
<TextBlock Margin="1,3,3,3" Text="{Binding HoursPerAgentText}"/>
</StackPanel>
<TextBlock Grid.Row="1" Text="{x:Static resources:Strings.Utilization}" Margin="3" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding Utilization, ValidatesOnDataErrors=true, StringFormat='P2', Converter={StaticResource DecimalToPercentageConverter}}"
Margin="3" VerticalAlignment="Center" />
<TextBlock Grid.Row="2" Text="{x:Static resources:Strings.Shrinkage}" Margin="3" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding Shrinkage, ValidatesOnDataErrors=true, StringFormat='P2', Converter={StaticResource DecimalToPercentageConverter}}"
Margin="3" VerticalAlignment="Center"/>
</Grid>
</HeaderedContentControl>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</UserControl>
It renders like this:
My task is to implement paste functionality across all days and both fields. Users will copy the values from an Excel sheet and paste directly into one of the textboxes. I have the parsing logic down but I'm stumped on how to handle the pasting across all controls.
So far I tried with a CommandBinding with the Paste command but this seems to only give me an event in the view code behind, Ideally I'd like to be able to handle the logic in my view model. I know there are patterns that allow a command binding to be linked to a view model with an attached property (http://codingcontext.wordpress.com/2008/12/10/commandbindings-in-mvvm/) and in this case, I get the event in the viewmodel but then all I get is the sender as a textbox element (I don't want that in my viewmodel).
Has anyone come across the same problem or has ideas on how to solve this elegantly?
I have a Utility project that hosts many Manager classes, one of which is the ClipboardManager. All of my Manager classes follow the Singleton pattern (meaning that there can only be one of each) and are made available from the BaseViewModel class. They are all interfaced, so that I can implement mock managers for testing purposes, but that's another story.
The ClipboardManager has some standard methods, but essentially for specific tasks, I need to add the following methods:
/// <summary>
/// Copies the SecurityPermissions object specified by the securityPermissions input parameter into the computer clipboard in the SecurityPermissions format.
/// </summary>
/// <param name="securityPermissions">The SecurityPermissions object to copy into the computer clipboard.</param>
public void SetClipboardSecurityPermissions(SecurityPermissions securityPermissions)
{
try { SetClipboardSecurityPermissionsData(securityPermissions); }
catch (COMException)
{
Thread.Sleep(0);
try { SetClipboardSecurityPermissionsData(securityPermissions); }
catch (COMException)
{
MessageBox.Show("The clipboard was inaccessible, please try again later");
}
}
}
private void SetClipboardSecurityPermissionsData(SecurityPermissions userSecurityPermissions)
{
List<SecurityPermission> securityPermissions = new List<SecurityPermission>(userSecurityPermissions);
List<SerializableSecurityPermission> serializableSecurityPermissions = new List<SerializableSecurityPermission>();
foreach (SecurityPermission securityPermission in securityPermissions) serializableSecurityPermissions.Add(new SerializableSecurityPermission(securityPermission));
DataObject securityPermissionsDataObject = new DataObject();
securityPermissionsDataObject.SetData("SecurityPermissions", serializableSecurityPermissions, false);
Clipboard.SetDataObject(securityPermissionsDataObject);
}
/// <summary>
/// Retrieves a SecurityPermissions object from the computer clipboard if any data exists in the SecurityPermissions format.
/// </summary>
public SecurityPermissions GetClipboardSecurityPermissions()
{
if (HasClipboardGotDataFormat("SecurityPermissions"))
{
try { return PasteClipboardSecurityPermissions(); }
catch (COMException)
{
Thread.Sleep(0);
try { return PasteClipboardSecurityPermissions(); }
catch (COMException)
{
MessageBox.Show("The clipboard was inaccessible, please try again later");
}
}
}
return new SecurityPermissions();
}
private SecurityPermissions PasteClipboardSecurityPermissions()
{
object data = Clipboard.GetData("SecurityPermissions");
if (data != null)
{
List<SecurityPermission> securityPermissions = new List<SecurityPermission>();
List<SerializableSecurityPermission> serializableSecurityPermissions = (List<SerializableSecurityPermission>)data;
foreach(SerializableSecurityPermission serializableSecurityPermission in serializableSecurityPermissions) securityPermissions.Add(new SecurityPermission(serializableSecurityPermission));
return new SecurityPermissions(securityPermissions);
}
return new SecurityPermissions();
}
This uses one of the standard methods:
/// <summary>
/// Returns true if any data currently exists in the computer clipboard in the data format specified by the dataFormat input parameter.
/// </summary>
public bool HasClipboardGotDataFormat(string dataFormat)
{
return Clipboard.ContainsData(dataFormat);
}
The public methods are the ones that are interfaced and so the ones that I have access to from my view models. So this is one way to handle clipboard activity from the view model. Now once you have your data in the view model, how you distribute it amongst your various controls will very much depend on how you have set them up.
If you have one common parent view model, then I would perform your clipboard activity in there and then distributing the data to the child controls should be easy.
UPDATE >>>
One easy way to handle this is to provide a 'paste' Button in your UI. You can connect that to an ICommand of some sort in your parent view model... I use a custom ActionCommand (similar to RelayCommand):
/// <summary>
/// Pastes the SecurityPermissions collection from the computer clipboard to the currently selected User.
/// </summary>
public ICommand PasteUserSecurityPermissions
{
get { return new ActionCommand(action => ((SecurityViewModel)ViewModel).
PasteSecurityPermissions(ClipboardManager.GetClipboardSecurityPermissions()),
canExecute => ClipboardManager.HasClipboardGotDataFormat("SecurityPermissions")); }
}
Then in the child view model:
/// <summary>
/// Pastes the SecurityPermissions collection from the computer clipboard to the currently selected User object.
/// </summary>
public void PasteSecurityPermissions(SecurityPermissions securityPermissions)
{
// Do whatever you like with the pasted data here
}
In your case, you could have one of these paste methods in each of your child view models... nice and easy. :)

How can I raise a custom Routed Event from a DataTemplate?

I have a User Control,called dCB_Props that contains several objects, most importantly a ComboBox that's bound to an Observable Collection. Though the collection can take any object, it will normally take a UserControl called EditDeleteItem. I've set dCB_Props to use EditDeleteItem as an ItemsTemplate but the events aren't fired. If, on the other hand, I add an instance of EditDeleteItem then the events will get fired. I can't add items this way because the EditDeleteItem will host other controls and I'd need to use different DataTemplates.
EditDeleteItem has two Routed Events called EditClick and DeleteClick.
When the collection changes it fires an event that checks if the item added is of type EditDeleteItem. If so, then it adds handlers to the two aforementioned events.
Part of the xaml for EditDeleteClick:
<WrapPanel x:Name="wp" HorizontalAlignment="Right" Visibility="Hidden" VerticalAlignment="Center" Margin="0,0,5,0">
<Button x:Name="PART_Edit" Width="20" Height="20" Content="{DynamicResource dPen}" Style="{DynamicResource dTranspButton}" Click="btnEdit_Click"/>
<Button x:Name="PART_Delete" Width="20" Height="20" Content="{DynamicResource dCross}" Style="{DynamicResource dTranspButton}" Click="btnDelete_Click"/>
</WrapPanel>
<Label Content="{TemplateBinding Content}" Margin="2,0,45,0" Padding="0,0,0,0" HorizontalAlignment="Left" VerticalContentAlignment="Center"/>
Part of the xaml for dCB_Props:
<ComboBox HorizontalContentAlignment="Stretch" x:Name="PART_cb" Background="Transparent" Margin="0,0,0.367,0" d:LayoutOverrides="HorizontalAlignment" ItemsSource="{Binding Items, ElementName=dcb}" IsDropDownOpen="{Binding IsDropDownOpen,ElementName=dcb, Mode=TwoWay}" Grid.ColumnSpan="3" Style="{DynamicResource DaisyComboBox}" />
<Button x:Name="PART_Edit" Width="20" Height="20" Content="{DynamicResource dPen}" Visibility="Hidden" Style="{DynamicResource dTranspButton}" Margin="2.581,1.48,17.778,-1.48" Grid.Column="1" Click="btnEdit_Click"/>
<Button x:Name="PART_Delete" Width="20" Height="20" Content="{DynamicResource dCross}" Visibility="Hidden" Margin="22.602,1.48,-2.243,-1.48" Style="{DynamicResource dTranspButton}" Grid.Column="1" Click="btnDelete_Click"/>
<Button x:Name="PART_Add" Content="+" Grid.Column="3" Margin="0,0,0,0" Style="{DynamicResource dTranspButton}" Click="btnAdd_Click"/>
Note the above two are codes just for objects, I've left out Column Definitions, Event Triggers, etc.
Part of dCB_Props.xaml.cs code is:
public partial class dCB_Props : UserControl
{
public dCB_Props()
{
this.InitializeComponent();
Items= new ObservableCollection<object>();
Items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Items_CollectionChanged);
}
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (var o in e.NewItems)
{
if (o.GetType() == typeof(EditDeleteItem))
{
EditDeleteItem itm = (EditDeleteItem)o;
itm.EditClick += new RoutedEventHandler(ItemEdit_Click);
itm.DeleteClick += new RoutedEventHandler(ItemDelete_Click);
}
}
}
}
...//I've left some code here since I don't deem it's that important for the situation
private void ItemEdit_Click(object sender, RoutedEventArgs e)
{
DependencyObject d = GetTemplateChild("PART_cb");
if (d == null) return;
ComboBox cb = (ComboBox)d;
if (cb.SelectedItem != null) RaiseEvent(new RoutedEventArgs(EditClickEvent, e.OriginalSource));
}
}
The above works if I add an item of type EditDeleteItem and remove the ItemTemplate property for the Label that resides inside dCB_Props. It also works if I set the ItemTemplate, shown below, in EditDeleteItem's ContentTemplate. But, as mentioned, I need to use different Data Templates so I assume all Data Templates will have to reside in a Resource Dictionary and then I'd have to use a Template Selector.
Data Template:
<DataTemplate x:Shared="false" x:Key="TagTemplate">
<local:EditDeleteItem x:Name="edItem">
<local:EditDeleteItem.Content>
<StackPanel>
<TextBlock Text="{Binding Path=Content.Label}"/>
<CheckBox Content="Isolated" IsChecked="{Binding Content.IsIsolated}"/>
<CheckBox Content="Match Case" IsChecked="{Binding Content.MatchCase}"/>
<CheckBox Content="Include" IsChecked="{Binding Content.Include}"/>
</StackPanel>
</local:EditDeleteItem.Content>
</local:EditDeleteItem>
</DataTemplate>
I believe I need to use command bindings. But not really sure where to put the CommandBindings, and not so sure how to use them, though I've read a page or two.
Thanks,
Hassan
The events are fired, but you don't catch them, because subscription in Items_CollectionChanged never occurs if ItemTemplate is used.
You should understand how ItemsControl (and ComboBox) works with ItemsSource. ItemsControl use ItemContainerGenerator to populate its visual tree. Each item from ItemsSource wrap into container which derived from ContentControl. Then item is set as a Content, ItemTemplate is set as ContentTemplate and so on. When you put EditDeleteItem into ItemTemplate it becomes a part of visual tree but not an item. That's why there is no EditDeleteItem in e.NewItems and no subscription.
The right way is Commands, as you mentioned. You should declare two commands:
public class EditDeleteItem : UserControl
{
...
public static readonly RoutedUICommand EditCommand = new RoutedUICommand(...);
public static readonly RoutedUICommand DeleteCommand = new RoutedUICommand(...);
...
}
Now the part of template may look like:
<WrapPanel ...>
<Button ... Command="{x:Static EditDeleteItem.EditCommand}"/>
<Button ... Command="{x:Static EditDeleteItem.DeleteCommand}"/>
</WrapPanel>
Then you add command bindings to dCB_Props:
public partial class dCB_Props : UserControl
{
static dCB_Props()
{
...
CommandManager.RegisterClassCommandBinding(
typeof(dCB_Props),
new CommandBinding(EditDeleteItem.EditCommand, OnEditCommandExecuted));
CommandManager.RegisterClassCommandBinding(
typeof(dCB_Props),
new CommandBinding(EditDeleteItem.DeleteCommand, OnDeleteCommandExecuted));
...
}
...
}
You need to implement OnEditCommandExecuted and OnDeleteCommandExecuted in order to handle corresponding commands from EditDeleteItem.
I hope I understood your question correctly ;)

Categories

Resources