WPF MVVM itemsControl and button command does not work? - wpf

I have got a problem with my WPF MVVM application.
I have got a view (CaisseView.xaml) and its ViewModel (CaisseViewModel.cs which is used as Datacontext of the View called since another .cs).
Into my view i am using an itemsControl in order to have a button for each element of a observablecollection of a Model. Moreover into my view I have got an ListView. I want that every time a button of the itemsControl is clicked a new line appears into my Lisview.
This last thing is what it does not work!!
Using debug I enter into my command, the command add an object into my observable collection but the app does not actualize the View.
Into my ViewModel, I am using INotifyPropertyChanged, RelayCommand and ICommand.
I let you my code.
Can you help me with my matter ???
thanks:
My View :
<UserControl x:Class="LMarket.Views.CaisseView"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:LMarket.Views"
xmlns:localM="clr-namespace:LMarket.Models"
xmlns:localModel="clr-namespace:LMarket.ViewModels"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d">
<UserControl.Resources>
<localModel:CaisseViewModel x:Key="CaisseViewModel"/>
<localM:ProduitDuStock x:Key="ProduitDuStockModel"/>
</UserControl.Resources>
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Cursor="Hand" Source="/Resources/homepage.png" Width="32" Height="32" HorizontalAlignment="Left" Margin="15,10" Grid.Row="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding DataContext.OnHomeCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<Image Cursor="Hand" Source="/Resources/LogOut.png" Width="32" Height="32" HorizontalAlignment="Right" Margin="15,10" Grid.Row="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding DataContext.BackConnectionCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="2" BorderBrush="Azure" Margin="5,5,1,5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="1*" />
<RowDefinition Height="50" />
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="#FFC2E0ED">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Image Cursor="Hand" VerticalAlignment="Center" Width="32" Height="32" Source="/Resources/files-and-folders.png"/>
<Image Cursor="Hand" VerticalAlignment="Center" Margin="10,0" Width="32" Height="32" Source="/Resources/cancel.png"/>
</StackPanel>
</Grid>
<ListView Grid.Row="1" Margin="5" SelectionMode="Single"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Path=LstProducts, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate DataType="localM:ProduitDuStock">
<StackPanel Margin="2" MinWidth="244">
<StackPanel.Resources>
<Style TargetType="{x:Type StackPanel}">
<!-- <Style.Triggers>
<DataTrigger Binding="{Binding Path=Selected}" Value="True">
<Setter Property="Background" Value="LightBlue"/>
</DataTrigger>
</Style.Triggers> -->
</Style>
</StackPanel.Resources>
<DockPanel >
<TextBlock FontWeight="Bold" Text="Produit: " DockPanel.Dock="Left" Margin="5,0,5,0"/>
<TextBlock Text=" " />
<TextBlock Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="Green" FontWeight="Bold" />
</DockPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<DockPanel Grid.Row="2" Margin="5" Background="#FF296B88">
<Label Content="TOTAL" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" FontFamily="Rockwell Condensed" Foreground="#FFF2FF5A" />
<Label Content="$$" HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" FontFamily="Rockwell Condensed" Foreground="#FFF2FF5A" />
</DockPanel>
<Grid Grid.Row="3" Background="#FFC2E0ED">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2">
<Button Cursor="Hand" Width="80" Content="Cuenta %" Background="#FF98ADA8" BorderBrush="#FF4A6DFF" Margin="2,0">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8"/>
</Style>
</Button.Resources>
</Button>
<Button Cursor="Hand" Width="80" Content="Producto %" Background="#FF98ADA8" BorderBrush="#FF4A6DFF" Margin="2,0">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8"/>
</Style>
</Button.Resources>
</Button>
<Button Cursor="Hand" Width="80" Content="-1" Background="#FFDDCF00" BorderBrush="#FF4A6DFF" FontWeight="Bold" FontSize="20">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8"/>
</Style>
</Button.Resources>
</Button>
<Button Cursor="Hand" Width="80" Content="+1" Background="#FF79C837" BorderBrush="#FF4A6DFF" FontWeight="Bold" FontSize="20" Margin="2,0">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8"/>
</Style>
</Button.Resources>
</Button>
<Button Cursor="Hand" Width="auto" Content=" Validar para Pagar " Command="{Binding AddNewProductCommand}" Background="#FF98ADA8" BorderBrush="#FF4A6DFF" FontSize="19" Margin="80,0,2,0" Foreground="Black">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8"/>
</Style>
</Button.Resources>
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<Border Grid.Column="1" BorderThickness="2" BorderBrush="Azure" Margin="1,5,5,5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" MaxHeight="720"/>
<RowDefinition Height="1" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Background="Transparent"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding ListOfProducts}" MaxWidth="762">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Cursor="Hand" CornerRadius="8" Margin="4,3" BorderThickness="1.5" BorderBrush="#FFFDFF00">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF6F92DC" Offset="1"/>
<GradientStop Color="#FFA6DBFF" Offset="0.534"/>
</LinearGradientBrush>
</Border.Background>
<Button BorderBrush="Transparent" Background="Transparent" Command="{Binding Source={StaticResource CaisseViewModel}, Path=AddNewProductCommand}" CommandParameter="{Binding Id}">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="8"/>
</Style>
</Button.Resources>
<StackPanel Orientation="Vertical" Margin="2" Width="80" Height="80">
<Image Source="{Binding ProductImageUrl}" Width="40" Height="40" Margin="0,10,0,5"
HorizontalAlignment="Center"/>
<Label Content="{Binding Surname}" FontWeight="Bold" HorizontalAlignment="Center" FontSize="11"/>
</StackPanel>
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Border>
</Grid>
</Grid>
</UserControl>
And my ViewModel:
using LMarket.Models;
using LMarket.Views;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Xml;
namespace LMarket.ViewModels
{
class CaisseViewModel : INotifyPropertyChanged
{
//Event for INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string str = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(str));
}
#region Attributs
private ObservableCollection<ProduitDuStock> listOfProducts;
private ObservableCollection<ProduitDuStock> lstProducts;
private ProduitDuStock selectedProduct;
#endregion
#region Getter/Setter
public ObservableCollection<ProduitDuStock> ListOfProducts
{
get
{
return listOfProducts;
}
set
{
if (listOfProducts != value)
{
listOfProducts = value;
}
NotifyPropertyChanged();
}
}
public ObservableCollection<ProduitDuStock> LstProducts
{
get
{
return lstProducts;
}
set
{
if (lstProducts != value)
{
lstProducts = value;
}
NotifyPropertyChanged();
}
}
public ProduitDuStock SelectedProduct
{
get
{
return selectedProduct;
}
set
{
if (selectedProduct != value)
{
selectedProduct = value;
NotifyPropertyChanged();
}
}
}
#endregion
#region Constructeur
public CaisseViewModel()
{
ListOfProducts = new ObservableCollection<ProduitDuStock>();
LstProducts = new ObservableCollection<ProduitDuStock>();
toto();
//Lecture du fichier ou son enregistrer les données du stock (En local sur un .xml)
try
{
//Ouverture du document XML
XmlDocument xmlDoc = new XmlDocument();
string Path = Directory.GetCurrentDirectory().ToString();
Path = Path + "\\Configuration\\Stockes.xml";
xmlDoc.Load(Path);
//Recupérer la section des produits
XmlNodeList nodeList = xmlDoc.DocumentElement.SelectNodes("/Stockes/Products/product");
//Pour chaque produit dans le fichier, créer un objet ProduitDuStock et l'ajouter à l'ObservableCollection.
foreach (XmlNode node in nodeList)
{
//Creation du nouvel objet
ProduitDuStock testProduit = new ProduitDuStock();
foreach (XmlAttribute att in node.Attributes)
{
//Récupération des parametres de l'objet.
switch (att.Name)
{
case "id":
testProduit.Id = int.Parse(att.Value);
break;
case "name":
testProduit.Name = att.Value;
break;
case "pays":
testProduit.Pays = att.Value;
break;
case "surname":
testProduit.Surname = att.Value;
break;
case "quantite":
testProduit.Quantite = int.Parse(att.Value);
break;
case "unitprice":
testProduit.UnitPrice = int.Parse(att.Value);
break;
case "productImageUrl":
testProduit.ProductImageUrl = att.Value;
break;
default:
break;
}
}
//Ajouter l'objet créé a notre ObservableCollection.
ListOfProducts.Add(testProduit);
}
}
catch (Exception ex)
{
}
}
#endregion
#region Command Definition
//Event RelayCommand Definition
private RelayCommand addProductToBill = null;
public ICommand AddProductToBill
{
get
{
if (addProductToBill == null)
{
addProductToBill = new RelayCommand(OnAddProductToBill);
}
return addProductToBill;
}
}
private RelayCommand addNewProductCommand = null;
public ICommand AddNewProductCommand
{
get
{
if (addNewProductCommand == null)
{
addNewProductCommand = new RelayCommand(OnAddNewProductCommand);
}
return addNewProductCommand;
}
}
#endregion
#region Command function definition
//Event lorsque le curseur rentre dans la zone du boutton "Stock"
private void OnAddNewProductCommand(object obj)
{
toto();
}
private void OnAddProductToBill(object obj)
{
toto();
}
#endregion
#region function
public void toto()
{
ProduitDuStock myAddProductToBill = new ProduitDuStock();
myAddProductToBill.Name = "trrt";
LstProducts.Add(myAddProductToBill);
}
#endregion
}
}
the important part into the view is the next:
<Button BorderBrush="Transparent" Background="Transparent" Command="{Binding Source={StaticResource CaisseViewModel}, Path=AddNewProductCommand}" CommandParameter="{Binding Id}">
remember that this button is defined into a itemsControl ! So the Datacontextof the button is not my viewModel that why I am using a staticResource...
I hope something will help me :) thank you very much.

Welcome to SO! For future reference, you'll have a much better chance of getting your questions answered if you post an MCVE, there's enough unnecessary code in what you posted to put most people here off.
To answer your question, there are actually many things wrong with this code, but the main issue is that you're creating callback handlers of type RelayCommand, yet your command handler (AddProductToBill) expects an object parameter and should thus be of type RelayCommand<object>. (More confusingly, one of your bindings is passing in CommandParameter, while the other is not).
Another thing to avoid is declaring your view model in your resources block, this is generally not done for a variety of reasons (are you sure you're assigning that instance as your DataContext and not declaring a new one?). If you're assigning your DataContext elsewhere then your list elements can bind to it with something like this:
<UserControl ...
x:Name="_this">
.
.
<!-- Inside ItemsControl.ItemTemplate -->
<Button
Command="{Binding ElementName=_this, Path=DataContext.MyHandler}"
CommandParameter="{Binding}" ... />

Related

WPF Dependency Property Bound To User Control

I have a button subclass called MenuButton.
public class MenuButton : Button
{
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MenuButton), new UIPropertyMetadata(null));
public UserControl Icon
{
get { return (UserControl)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(UserControl), typeof(MenuButton),
new PropertyMetadata(null));
}
In the style I want to show an icon that was created using paths from SVG files. I have created a User Control containing the XAML for the icon:
<UserControl x:Class="WpfApplication1.Views.ScopeIcon"
.
.
.
>
<Viewbox Height="55"
Width="55">
<Grid>
<Path Fill="LightBlue" Data="M98.219,48.111C97..."/>
<Path Fill="LightBlue" Data="M98.219,46.948C97...."/>
</Grid>
</Viewbox>
</UserControl>
And here's the style:
<Style TargetType="Button"
x:Key="TestButtonStyle">
<Setter Property="Height" Value="140"/>
<Setter Property="Width" Value="195"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MenuButton}">
<Border x:Name="TheBorder">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Row="0"/> <===== THE USER CONTROL WILL GO HERE
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="{TemplateBinding Caption}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="5"
Foreground="White"
FontSize="14"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I will set it here:
<ListBox Grid.Row="0"
ItemsSource="{Binding MainTools}" >
<ListBox.ItemTemplate>
<DataTemplate>
<controls:MenuButton Caption="{Binding Caption}"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}"
Icon=""/> <============= HOW DO I PUT THE SCOPEICON USER CONTROL HERE?
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm essentially trying to nest user controls, but I want to tell the button, in XAML, what UserControl to use for its icon.
Thank you
First, you need to update your style to set Content property on your ContentPresenter the same way you did for Text property on TextBlock.
<ContentPresenter Grid.Row="0" Content="{TemplateBinding Icon}"/>
And, then to set Icon on the MenuButton in your DataTemplate update your MenuButton element in DataTemplate to something like:
<controls:MenuButton Caption="Caption"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}">
<controls:MenuButton.Icon>
<views:ScopeIcon />
</controls:MenuButton.Icon>
</controls:MenuButton>

Combobox with custom dropdown(Popup) template and more

I am having a problem hooking up some events in my custom dropdown or Popup template in my combobox.
This custom template by the way looks like the one in IE 10. Here's the picture
I got the partial look (the one below is mine). But I'm having a problem deleting an item in history list. Below is my PART_Popup template
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent" MinWidth="{Binding ActualWidth, ElementName=Placement}">
<Border x:Name="DropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<!--<ScrollViewer>-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel>
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Border Padding="5">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="History" Foreground="{StaticResource SeparatorLine}" />
<Border Height="2" Grid.Column="1" Margin="5,0,0,0" BorderBrush="{StaticResource SeparatorLine}" BorderThickness="0.5" />
</Grid>
<ListBox x:Name="listHistory" BorderThickness="0" Margin="0" Padding="0" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding }" />
<Button Grid.Column="1" HorizontalAlignment="Right" x:Name="btnDeleteHistoryItem" Content="r" FontFamily="Marlett" Style="{DynamicResource ButtonStyle}" Visibility="Hidden" Opacity="0.75" />
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Visibility" TargetName="btnDeleteHistoryItem" Value="Visible" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</StackPanel>
<Border Grid.Row="1" BorderBrush="{StaticResource SeparatorLine}" BorderThickness="0,1,0,0" Padding="5" Height="30">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#33000000" Offset="0"/>
<GradientStop Offset="1"/>
<GradientStop Offset="0.375"/>
</LinearGradientBrush>
</Border.Background>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="btnClearHistory" Content=" Clear History " Style="{DynamicResource ButtonStyle}" />
</StackPanel>
</Border>
</Grid>
<!--</ScrollViewer>-->
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
alright, in the Listbox(listHistory) I have a button there called btnDeleteHistoryItem and I cannot hook it. It's returning a Null error in my code here
protected override void OnSourceInitialized(EventArgs e)
{
ListBox lb = (ListBox)cbSearch.Template.FindName("listHistory", cbSearch);
lb.ItemsSource = this.SearchHistory;
lb.SelectionChanged += cbResults_SelectionChanged;
Button btnDeleteHistoryItem = (Button)lb.Template.FindName("btnDeleteHistoryItem", lb);
// if (btnDeleteHistoryItem != null)
{
btnDeleteHistoryItem.Click += DeleteHistoryItem_Click;
}
// or --------------
Button btnDeleteHistoryItem = (Button)cbSearch.Template.FindName("btnDeleteHistoryItem", cbSearch);
// if (btnDeleteHistoryItem != null)
{
btnDeleteHistoryItem.Click += DeleteHistoryItem_Click;
}
}
Coding is a little classic here, so no MVVM implemented in hooking with events. So the problem is this Button btnDeleteHistoryItem = (Button)lb.Template.FindName("btnDeleteHistoryItem", lb); or Button btnDeleteHistoryItem = (Button)cbSearch.Template.FindName("btnDeleteHistoryItem", cbSearch);
line where it cannot find that btnDeleteHistoryItem.
How can I resolve this?
-- UPDATE --
lb.Loaded += (a, b) =>
{
Button btnDeleteHistoryItem = (Button)lb.Template.FindName("btnDeleteHistoryItem", lb);
//if (btnDeleteHistoryItem != null)
{
btnDeleteHistoryItem.Click += DeleteHistoryItem_Click;
}
};
doesn't work either
Using a command will probably be the easiest solution here. You can hook a command up to your delete buttons and pass the current item in as the parameter. Here's a simple sample to illustrate.
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
SomeCommand = new MySampleCommand();
cbo.Items.Add("Hello");
cbo.Items.Add("Item 1");
cbo.Items.Add("Another Item");
cbo.Items.Add("Something else");
cbo.Items.Add("Yet another item");
}
public MySampleCommand SomeCommand { get; set; }
}
public class MySampleCommand : ICommand {
public bool CanExecute(object parameter) {
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) {
MessageBox.Show(string.Format("You are trying to remove {0}", parameter.ToString()));
}
}
Your combobox binding would look something like:
<Window x:Class="SOComboWithEmbeddedButton2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="ListBoxItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Command="{Binding SomeCommand, ElementName=Window}" CommandParameter="{Binding}" Content="Remove" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ComboBox ItemTemplate="{StaticResource ListBoxItemTemplate}" x:Name="cbo" />
</StackPanel>
Just adjust the Command binding on the DataTemplate to point to the appropriate object that has an instance of your command object. Hope that helps!

Sharing control instance within a view in WPF

I'm having some issues with the wpf tab control. Not sure the title of the question is right I will refine it accoring to answers.
I want to create a simple panel system. I want to inject ot my "panel viewModel" 2 view model
MainViewModel will be display as the main area
PanelViewModel will be display as a panel on the right hand side of the view
the panelViewModel will be hidden by default and a button will display it on top of the main view model when needed
The view look like this:
<UserControl.Resources>
<DataTemplate x:Key="MainWindowTemplate" DataType="{x:Type UserControl}">
<ContentPresenter Content="{Binding DataContext.MainViewModel, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" ContentTemplate="{StaticResource MainWindowTemplate}" />
<Button Grid.Column="1" Content="{Binding PanelTitle}" Command="{Binding Path=ShowPanelCommand}">
<Button.LayoutTransform>
<RotateTransform Angle="90"/>
</Button.LayoutTransform>
</Button>
</Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl ContentTemplate="{StaticResource MainWindowTemplate}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="2" VerticalAlignment="Stretch" Background="Red" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border HorizontalAlignment="Stretch">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding PanelTitle}" Margin="5,5,0,2" HorizontalAlignment="Left"></TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0,0,5,0" HorizontalAlignment="Right" >
<Button Content="Minimze" Command="{Binding HidePanelCommand}"/>
</StackPanel>
</Grid>
</Border>
<ContentPresenter Grid.Row="1" Margin="2" Content="{Binding PanelViewModel}" VerticalAlignment="Top"/>
</Grid>
</Border>
</Grid>
</Grid>
The view model look like that:
public class TestTabViewModel : ObservableObject
{
#region private attributes
#endregion
public TestTabViewModel(string panelName, object panelViewModel, object mainViewModel)
{
IsPanelHidden = true;
PanelTitle = panelName;
PanelViewModel = panelViewModel;
MainViewModel = mainViewModel;
ShowPanelCommand = new DelegateCommand(() =>ManagePanelVisibility(true));
HidePanelCommand = new DelegateCommand(() => ManagePanelVisibility(false));
}
#region properties
public string PanelTitle { get; private set; }
public bool IsPanelHidden { get; private set; }
public object PanelViewModel { get; private set; }
public object MainViewModel { get; private set; }
public DelegateCommand ShowPanelCommand { get; private set; }
public DelegateCommand HidePanelCommand { get; private set; }
#endregion
#region private methods
private void ManagePanelVisibility(bool visible)
{
IsPanelHidden = !visible;
RaisePropertyChanged(() => IsPanelHidden);
}
#endregion
}
So for so good, this system work fine I aslo added some pin command but I remove them from here to make it "simple".
My problem come when the main view model hold a tab control. In this, case if I select a tab and "open" the panel, the tab selected is "changed". In fact it's not changed it's just that I display another contentControl which is not synchronize with the previouse one. I guess that the view instance is not the same even if the viewmodel behind is.
So how do I share a view instance within a view (or have the selection process synchornized)? My first guest was to use the datatemplate (as show in the example) but it did not solve my problem.
By the way, I know some third-party handling panel docking pin ... (eg avalon) but all the one I found are really too much for my simple need.
Thanks for the help
Your best bet would probably be to replace your two Grids with a single ContentControl, and switch the Template on button click or in a Trigger. This way your actual Content (the TabControl) will be the same, but the template used to display the Content will change
Here's a quick example:
<Window.Resources>
<ControlTemplate x:Key="Grid1Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Left" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
<ControlTemplate x:Key="Grid2Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Right" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="btnToggle" Content="Toggle View" DockPanel.Dock="Top" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template" Value="{StaticResource Grid1Template}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=btnToggle, Path=IsChecked}" Value="True">
<Setter Property="Template" Value="{StaticResource Grid2Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<TabControl>
<TabItem Header="Tab1" />
<TabItem Header="Tab2" />
<TabItem Header="Tab3" />
</TabControl>
</ContentControl>
</DockPanel>
i dont know if i get what you want but i think that you could do the following.
i assume that you want some "MainViewmodeldata" be presented as your tabcontrol.
so i woulf first create a datatemplate for this.
<UserControl.Resources>
<DataTemplate DataType="{x:Type MainViewmodeldata}">
<TabControl>
<TabItem Header="Tab1">
<TextBlock Grid.Column="1" Text="Tab1Content"/>
</TabItem>
<TabItem Header="Tab2">
<TextBlock Grid.Column="1" Text="Tab2Content"/>
</TabItem>
</TabControl>
</DataTemplate>
</UserControl.Resources>
now i would just bind my this mainviewmodeldata to the contentcontrol and let wpf render it for you. i really dont know if you still need these two grids, cause i dont know what you wanna achieve.
<Grid x:Name="Grid1" Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel Grid.Column="1">
<Button x:Name="Button1" Content="Switch look" Command="{Binding ShowPanelCommand}"/>
<TextBlock Text="Look1"/>
</StackPanel>
</Grid>
<Grid x:Name="Grid2" Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel HorizontalAlignment="Right">
<Button x:Name="Button2" Content="Switch look" Command="{Binding HidePanelCommand}"/>
<TextBlock Text="Look2"/>
</StackPanel>
</Grid>
</Grid>

Binding TabItem Header to Textblock in Style?

I'm trying to create a tab control in which the Header on the TabItem is binded in a textbox that is in the controltemplate of the tabcontrol. how can i do this through binding in the style?
Here's my code:
<Style x:Key="TabControlTest1" TargetType="TabControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--Area for TabItems-->
<Border Grid.Column="0"
Grid.Row="0"
>
<TabPanel IsItemsHost="True"
x:Name="HeaderPanel"
Background="Transparent" />
</Border>
<!--Content of SelectedItems-->
<Border Grid.Column="1"
BorderBrush="{DynamicResource TabControlContentPresentBorderBrush}"
BorderThickness="0,1,1,1"
Background="{DynamicResource TabControlContentPresentBackgroundBrush}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--///This is Where I want to bind the Header///-->
<Label
Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding Header, ElementName=TabItem}"/>
<ContentPresenter
Grid.Row="1"
x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"
Margin="5"/>
</Grid>
</Border>
<ControlTemplate x:Key="TabItemControlTemplate" TargetType="{x:Type TabItem}">
<!--Grid Defines Height and also hold content header-->
<Grid>
<Border Background="{DynamicResource TabItemContentPresentBackgroundBrush}"
Margin="0,0,0,5">
<!--Content of TabItem will be rendered-->
<ContentPresenter
x:Name="ContentSite"
Margin="3"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RecognizesAccessKey="True"
ContentSource="Header"/>
</Border>
</Grid>
</ControlTemplate>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
try this
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}
, Path=SelectedItem.Header}"/>
You could try and achieve this either using TemplateBinding or using RelativeSource.
TemplateBinding:
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{TemplateBinding Header}"/>
RelativeSource:
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
This isn't using a control template, but demonstrates binding a textbox to the tab header through a view model. Note that I'm using MVVM light (ViewModelBase and Set()), but you can replace with your own INotifyPropertyChanged support if necessary.
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type Samples:TabBindingViewModel}">
<StackPanel>
<TextBlock Text="{Binding MyContent}"/>
<TextBox Text="{Binding Header}"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<TabControl
ContentTemplate="{StaticResource ContentTemplate}"
DisplayMemberPath="Header"
ItemsSource="{Binding Items}" />
</Grid>
public class TabContentToHeaderViewModels : ViewModelBase
{
private readonly ObservableCollection<TabContentToHeaderViewModel> _items;
public TabContentToHeaderViewModels()
{
_items = new ObservableCollection<TabContentToHeaderViewModel>
{
new TabContentToHeaderViewModel(1),
new TabContentToHeaderViewModel(2),
new TabContentToHeaderViewModel(3),
};
}
public IEnumerable<TabContentToHeaderViewModel> Items
{
get { return _items; }
}
}
public class TabContentToHeaderViewModel : ViewModelBase
{
public TabContentToHeaderViewModel() : this(0)
{
}
public TabContentToHeaderViewModel(int n)
{
Header = "I'm the header: " + n.ToString(CultureInfo.InvariantCulture);
MyContent = "I'm the content: " + n.ToString(CultureInfo.InvariantCulture);
}
private string _header;
public string Header
{
get { return _header; }
set { Set(() => Header, ref _header, value); }
}
public string MyContent { get; set; }
}

click of a button defined inside DataTemplate

i have a template defiend as below:
<Window.Resources>
<DataTemplate x:Key="TemplateDetailView">
<Expander x:Name="exp" IsExpanded="True" FontSize="13" FontWeight="SemiBold">
<Expander.Header>
<TextBlock Foreground="{DynamicResource BlackColorBrush}">
<TextBlock.Text>
<MultiBinding StringFormat="{} {0} {1}">
<Binding Path="persons.Count"/>
<Binding Path="DisplayText"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Expander.Header>
<ListBox x:Name="lst" ItemsSource="{Binding persons}" Grid.Row="1" BorderThickness="0" Foreground="{DynamicResource BlackColorBrush}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="stretch"/>
<Setter Property="Background" Value="Transparent" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border DataContext="{Binding}" BorderThickness="2" Margin="0,0,0,-1" BorderBrush="{DynamicResource NormalBorderBrush}" Visibility="{Binding IsVisibility}">
<DockPanel Margin="15,5" Background="Transparent">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Background="Transparent">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" VerticalContentAlignment="Center" Tag="{Binding}" Padding="4,0" Style="{DynamicResource CheckBoxStyleDetailedViewStyle}" Checked="CheckBox_Checked" IsChecked="{Binding Acknowledged}" Height="30" Margin="3,5,3,3" Width="Auto"/>
</StackPanel> <ScrollViewer CanContentScroll="True" MinHeight="25" DockPanel.Dock="Left" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<ItemsControl ScrollViewer.CanContentScroll="True" DataContext="{Binding}" ItemsSource="{Binding AlertActionsDefinition.Children}" x:Name="lstButton" ButtonBase.Click="lstButton_Click">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Background="Transparent" >
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate >
<DataTemplate>
<Button Click="lstButton_Click" Content="{Binding Text}" Tag="{Binding}" Padding="4,0" IsEnabled="{Binding Visible}" Visibility="{Binding Visibility}" Height="30" Margin="3,5,3,3"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer> </StackPanel>
<Grid Width="700">
<Grid.RowDefinitions>
<RowDefinition Height="3"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3"/>
<RowDefinition Height="*"/>
<RowDefinition Height="3"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Text}" Grid.Row="1" FontWeight="Bold" FontSize="14"/>
<TextBlock Text="{Binding Description}" Grid.Row="3" FontSize="13"/>
</Grid>
</DockPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=lst,Path=Items.Count}" Value="0">
<Setter Property="Visibility" Value="Collapsed" TargetName="exp"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="listBox" ItemTemplate="{DynamicResource TemplateShortView}" ItemsSource="{Binding}" BorderThickness="0">
</ListBox>
</Grid>
listbox is binded to class persons. initially the list box is loaded with short view which is then loaded to have detailed view template defined above. the problem i am facing is the click event on button inside template. click wont be fired at all some times and some times takes two to three clicks to raise the event.
could any one help me out to trace it down ?
I reduced your code to suit my testing and it works perfectly for me! I receive all the events correctly. In fact I receive lstButton_Click twice for each button clicked... (due to bubbling at Button and ItemsControl level).
Code Behind...
/// <summary>
/// Interaction logic for Window6.xaml
/// </summary>
public partial class Window6 : Window
{
public Window[] JustList
{
get
{
return new Window[] { this };
}
}
public List<Person> persons
{
get
{
return new List<Person>()
{
new Person()
{
Acknowledged = true,
Description = "Person 1",
Text = "Person1",
DisplayText = "I am Person 1"
},
new Person()
{
Acknowledged = true,
Description = "Person 2",
Text = "Person2",
DisplayText = "I am Person 2"
}
};
}
}
public Window6()
{
InitializeComponent();
}
void lstButton_Click(object sender, RoutedEventArgs e)
{
var i = 0 ;
}
void CheckBox_Checked(object sender, RoutedEventArgs e)
{
var i = 0;
}
}
public class Person
{
public string DisplayText { get; set; }
public string Text { get; set; }
public bool Acknowledged { get; set; }
public string Description { get; set; }
public Visibility IsVisibility { get; set; }
public List<Person> Children
{
get
{
return new List<Person>()
{
new Person()
{
Acknowledged = true,
Description = "Child 1",
Text = "Child1",
DisplayText = "My Child 1"
},
new Person()
{
Acknowledged = true,
Description = "Child 2",
Text = "Child2",
DisplayText = "My Child 2"
}
};
}
}
}
XAML ...
<Window.Resources>
<DataTemplate x:Key="TemplateDetailView">
<Expander x:Name="exp" IsExpanded="True"
FontSize="13" FontWeight="SemiBold">
<Expander.Header>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{} {0} {1}">
<Binding Path="persons.Count"/>
<Binding Path="DisplayText"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Expander.Header>
<ListBox x:Name="lst" ItemsSource="{Binding persons}"
Grid.Row="1" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment"
Value="stretch"/>
<Setter Property="Background" Value="Transparent" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border DataContext="{Binding}"
BorderThickness="2"
Margin="0,0,0,-1" BorderBrush="Red">
<DockPanel Margin="15,5"
Background="Transparent">
<StackPanel DockPanel.Dock="Bottom"
Orientation="Horizontal"
Background="Transparent">
<StackPanel HorizontalAlignment="Left"
Orientation="Horizontal">
<CheckBox VerticalAlignment="Center"
VerticalContentAlignment="Center"
Tag="{Binding}"
Padding="4,0"
Checked="CheckBox_Checked"
IsChecked="{Binding Acknowledged}"
Height="30"
Margin="3,5,3,3" Width="Auto"/>
</StackPanel>
<ScrollViewer CanContentScroll="True"
MinHeight="25"
DockPanel.Dock="Left"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled">
<ItemsControl
ScrollViewer.CanContentScroll="True"
DataContext="{Binding}"
ItemsSource="{Binding Children}"
x:Name="lstButton"
ButtonBase.Click="lstButton_Click">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Orientation="Horizontal"
Background="Transparent" >
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate >
<DataTemplate>
<Button Click="lstButton_Click"
Content="{Binding Text}"
Tag="{Binding}"
Padding="4,0"
IsEnabled="{Binding
Visible}"
Visibility="{Binding
Visibility}"
Height="30"
Margin="3,5,3,3"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
<Grid Width="700">
<Grid.RowDefinitions>
<RowDefinition Height="3"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="3"/>
<RowDefinition Height="*"/>
<RowDefinition Height="3"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Text}" Grid.Row="1"
FontWeight="Bold" FontSize="14"/>
<TextBlock Text="{Binding Description}"
Grid.Row="3" FontSize="13"/>
</Grid>
</DockPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=lst,Path=Items.Count}"
Value="0">
<Setter Property="Visibility" Value="Collapsed" TargetName="exp"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="listBox" ItemTemplate="{StaticResource TemplateDetailView}"
ItemsSource="{Binding RelativeSource={RelativeSource
AncestorType={x:Type Window}},
Path=JustList}"
BorderThickness="0">
</ListBox>
</Grid>

Resources