Binding a listbox inside a custom control .Cannot Bind it - wpf

Binding a listbox inside a custom control
I am building a custom control which will have a listbox inside.I have posted here the simplified version
I cannot seem to be displaying a property of my bound viewModel."Surname".
Could you see what I am doing wrong?
Generic.Xaml (inside CustomControl)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1.SimpleList">
<Style TargetType="{x:Type local:SimpleListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SimpleListControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid ShowGridLines="False"
DataContext="{Binding RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type local:SimpleListControl}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="0" Grid.Row="0">
<ListBox Name="lstLeft"
MinHeight="200"
ItemsSource="{Binding LeftSideList, RelativeSource={RelativeSource TemplatedParent}}"
DisplayMemberPath="{Binding DisplayMemberPath, RelativeSource={RelativeSource TemplatedParent}}"
SelectionMode="Extended"
IsSynchronizedWithCurrentItem="True"
SelectedIndex="0" />
</StackPanel>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
public class SimpleListControl : Control
{
static SimpleListControl()
{
DefaultStyleKeyProperty
.OverrideMetadata(typeof(SimpleListControl),
new FrameworkPropertyMetadata(typeof(SimpleListControl)));
LeftSideListProperty = DependencyProperty.Register("LeftSideList",
typeof(IEnumerable),
typeof(SimpleListControl),
new PropertyMetadata(OnLeftItemsSourceChanged));
DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath",
typeof(string),
typeof(SimpleListControl),
new UIPropertyMetadata(string.Empty));
}
public readonly static DependencyProperty LeftSideListProperty;
public ObservableCollection<object> LeftSideList
{
get{return new ObservableCollection<object>{GetValue(LeftSideListProperty)};}
set { SetValue(LeftSideListProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty;
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private static void OnLeftItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//TODO.
}
}
CustomerView
<Window x:Class="WpfApp1.Views.CustomersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:simpleList="clr-namespace:WpfCustomControlLibrary1.SimpleList;assembly=WpfCustomControlLibrary1"
xmlns:viewModels="clr-namespace:WpfApp1.ViewModels"
Title="CustomersViewq" Height="300" Width="300">
<Window.Resources>
<viewModels:CustomersViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid>
<simpleList:SimpleListControl Name="SimpleListControl1"
LeftSideList="{Binding Source={StaticResource ViewModel},Path=AvailableCustomers}"
DisplayMemberPath="Surname" Margin="0,24,0,12" />
</Grid>
</Window>
CustomersViewModel
public class CustomersViewModel:ViewModelBase
{
public CustomersViewModel()
{
availableCustomers = new ObservableCollection<CustomerViewModel>();
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo1", Surname = "Bloggs1" });
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo2", Surname = "Bloggs2" });
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo3", Surname = "Bloggs3" });
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo4", Surname = "Bloggs4" });
}
private ObservableCollection<CustomerViewModel> availableCustomers;
public ObservableCollection<CustomerViewModel> AvailableCustomers
{
get { return availableCustomers; }
set
{
if (availableCustomers == value) return;
availableCustomers = value;
OnPropertyChanged("AvailableCustomers");
}
}
}
CustomerViewModel
public class CustomerViewModel : ViewModelBase
{
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName == null) return;
firstName = value;
OnPropertyChanged("FirstName");
}
}
private string surname;
public string Surname
{
get { return surname; }
set
{
if (surname == null) return;
surname = value;
OnPropertyChanged("Surname");
}
}
}

Whatever you trying to achieve, it seems you are over complicating things.
However if you need to display Surname you need to set it in the DisplayMemberPath as DisplayMemberPath="Surname".
Here's a tutorial on Custom Controls: How to Create Custom Control.
Below is a link for creating re-useable User control:
Creating Re-useable User Controls.

Related

Issues in WPF - MVVM - ViewNavigation not working when cliking button in UserControl

I am new to WPF MVVM. I have MainWindow.xaml here Grid is splited into two columns. Column1 hooked with MenuUserControl.xaml which has two button called Page1 and Page2. Column2 hooked with ContentControl which is used to display view based on button click in MenuUserControl.xaml. View navigation not working when I click the button. Please give me a working solution.
I have ViewModel for MainWindowViewModel.cs and MenuuserControlViewModel.cs also Page1UserControlViewModel and Page2UserControlViewModel. MainWindowViewModel contains property called SelectedViewModel which is binded with ContentControl
<ContentControl x:Name="Pages" Content="{Binding SelectedViewModel}"/>
MainWindow.Xaml
<Window x:Class="WpfNavigationViewTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfNavigationViewTestApplication.Views"
xmlns:viewModel="clr-namespace:WpfNavigationViewTestApplication.ViewModel"
mc:Ignorable="d"
Title="Master Part List" Width="1250" Height="700" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<viewModel:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:Page1ViewModel}">
<local: Page1UserControlView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:Page2ViewModel}">
<local: Page2UserControlView/>
</DataTemplate>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="125" />
<RowDefinition Height="*" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GridSplitter Grid.Row="1" Grid.ColumnSpan="0" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="10" Background="Gray" ShowsPreview="True" IsEnabled="False" />
<Grid Name="MenuGrid" Grid.Row="1" Grid.Column="0" Margin="3,13,3,3">
<local:MplMenuView DataContext="{Binding Path= MplMenuViewModel}" />
</Grid>
<Grid Name="PageGrid" Grid.Row="1" Grid.Column="1" Margin="13,13,3,3">
<ContentControl x:Name="Pages" Content="{Binding SelectedViewModel}"/>
</Grid>
</Grid>
</Grid>
</ScrollViewer>
</Window>
MainWindowViewModel.cs
public class MainWindowViewModel: INotifyPropertyChanged
{
private BaseViewModel selectedViewModel;
public BaseViewModel SelectedViewModel
{
get { return selectedViewModel; }
set
{
selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
MenuUserControl.Xaml
<UserControl x:Class="WpfNavigationViewTestApplication.Views. MenuUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfNavigationViewTestApplication.Views"
xmlns:viewModel="clr-namespace:WpfNavigationViewTestApplication.ViewModel"
mc:Ignorable="d"
Height="460" Width="154">
<UserControl.Resources>
<Style x:Key="menuButton" TargetType="Button">
<Setter Property="Height" Value="25"/>
<Setter Property="Width" Value="130"/>
<Setter Property="Margin" Value="0,4,0,0"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</UserControl.Resources>
<UserControl.DataContext>
<viewModel:MenuUserControlViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="105"/>
<RowDefinition Height="90" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" >
<Button Style="{StaticResource menuButton}" Content="Page1" Command="{Binding Page1Command}" />
<Button Style="{StaticResource menuButton}" Content="Page2" Command="{Binding Page2Command}"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Vertical" />
<StackPanel Grid.Row="2" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" >
</StackPanel>
</Grid>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Vertical" >
</StackPanel>
</Grid>
</UserControl>
MenuUserControlViewModel.cs
public class MenuUserControlViewModel : MainWindowViewModel
{
public ICommand Page1Command { get; set; }
public ICommand Page2Command { get; set; }
public MenuUserControlViewModel ()
{
Page1Command = new BaseCommand(OpenPage1View);
Page2Command = new BaseCommand(OpenPage2View);
}
private void OpenPage1View (object obj)
{
SelectedViewModel = new Page1UserControlViewModel ();
}
private void OpenPage2View (object obj)
{
SelectedViewModel = new Page2UserControlViewModel ();
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
: this(method, null)
{
}
public BaseCommand(Action<object> method, Predicate<object> canExecute)
{
_method = method;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
You are setting the SelectedViewModel property of another instance of MainWindowViewModel. MenuUserControlViewModel shouldn't inherit from MainWindowViewModel. It should communicate with the existing one.
Get rid of this from MenuUserControl.xaml:
<UserControl.DataContext>
<viewModel:MenuUserControlViewModel/>
</UserControl.DataContext>
...and create an instance of the MenuUserControlViewModel in the MainWindowViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private BaseViewModel selectedViewModel;
public BaseViewModel SelectedViewModel
{
get { return selectedViewModel; }
set
{
selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public MainWindowViewModel()
{
MplMenuViewModel = new MenuUserControlViewModel(this);
}
public MenuUserControlViewModel MplMenuViewModel { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public class MenuUserControlViewModel : MainWindowViewModel
{
private readonly MainWindowViewModel _mainWindowViewModel;
public ICommand Page1Command { get; set; }
public ICommand Page2Command { get; set; }
public MenuUserControlViewModel(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
Page1Command = new BaseCommand(OpenPage1View);
Page2Command = new BaseCommand(OpenPage2View);
}
private void OpenPage1View(object obj)
{
_mainWindowViewModel.SelectedViewModel = new Page1UserControlViewModel();
}
private void OpenPage2View(object obj)
{
_mainWindowViewModel.SelectedViewModel = new Page2UserControlViewModel();
}
}
MainWindow.xaml:
<local:MplMenuView DataContext="{Binding Path=MplMenuViewModel}" />

WPF TemplateBinding to an ObservableCollection

I'm creating a content control that contains another usercontrol. We'll call them InnerControl and OuterControl. The InnerControl has a dependency property of type ObservableCollection called "Items." I'm trying to bind that to an identical Dependency Property in the OuterControl. Here is a stub of the InnerControl code:
public class InnerControl : UserControl {
public InnerControl() {
InnerItems = new ObservableCollection<string>();
}
public ObservableCollection<string> InnerItems
{
get { return (ObservableCollection<string>)GetValue(InnerItemsProperty); }
set { SetValue(InnerItemsProperty, value); }
}
public static DependencyProperty InnerItemsProperty =
DependencyProperty.Register("InnerItems",
typeof(ObservableCollection<string>),
typeof(InnerControl),
new PropertyMetadata());
}
The outer control contains an identical Items property:
public class OuterControl : ContentControl {
public OuterControl() {
OuterItems = new ObservableCollection<string>();
}
static OuterControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(OuterControl),
new FrameworkPropertyMetadata(typeof(OuterControl)));
}
public ObservableCollection<string> OuterItems
{
get { return (ObservableCollection<string>)GetValue(OuterItemsProperty); }
set { SetValue(OuterItemsProperty, value); }
}
public static DependencyProperty OuterItemsProperty =
DependencyProperty.Register("OuterItems",
typeof(ObservableCollection<string>),
typeof(OuterControl),
new PropertyMetadata());
}
Then I'm defining the OuterControl's appearance in the generic.xaml file:
<Style TargetType="{x:Type userControls:OuterControl}">
<Setter Property="Padding" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type userControls:OuterControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:InnerControl Grid.Row="0" Grid.Column="0"
InnerItems="{TemplateBinding OuterItems}"/>
<ContentPresenter Grid.Row="1" Grid.Column="0"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The really important part of the above code I want to call your attention to is:
<local:InnerControl Grid.Row="0" Grid.Column="0"
InnerItems="{TemplateBinding OuterItems}"/>
What I expect to happen is that when items are added to the OuterItems collection of the OuterControl, those same items will be added to the InnerControl.InnerItems collection. However, that doesn't happen, and I can't figure out why.
I've also tried a relative binding so that I could experiment with using TwoWay mode and so on. Something like this:
InnerItems="{Binding OuterItems, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
But so far that hasn't worked either.
UPDATE
Everything that I thought solved this problem so far has only exposed new problems, so I've removed my previous updates. What I'm stuck with at this point is:
If I initialize InnerItems in the constructor, then the TemplateBinding doesn't seem to work (the items never get updated)
If I don't initialize InnerItems at all, the TemplateBinding works. However, if InnerControl is just used by itself in the Designer, it breaks, because InnerItems is null when the designer tries to add items to it.
When you have a collection type dependency property, you must not use an instance of the collection class as default value of the property. Doing so will make all instances of the control that owns the property use the same collection instance.
So your property metadata
new PropertyMetadata(new ObservableCollection<string>())
should be replaced by
new PropertyMetadata(null)
or you do not specify any metadata at all
public static DependencyProperty InnerItemsProperty =
DependencyProperty.Register(
"InnerItems", typeof(ObservableCollection<string>), typeof(InnerControl));
Now you would somehow have to initialize the property value. As usual, you'll do it in the control's constructor, like
public InnerControl()
{
InnerItems = new ObservableCollection<string>();
}
When you now bind the property of the control like
<local:InnerControl InnerItems="{Binding ...}" />
the value set in the constructor is replaced by the value produced by the Binding.
However, this does not happen when you create the Binding in a Style Setter, because values from Style Setters have lower precedence than so-called local values (see Dependency Property Value Precedence).
A workaround is to set the default value by the DependencyObject.SetCurrentValue() method, which does not set a local value:
public InnerControl()
{
SetCurrentValue(InnerItemsProperty, new ObservableCollection<string>());
}
I find it quite likely that #Clemens comment has the right answer. Anyhow, I tested your solution using the code below and it worked fine for me.
Check how you are binding and adding items. You did not post that code in your question.
OuterControl
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace TemplateBindingTest.Controls
{
public class OuterControl : UserControl
{
static OuterControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(OuterControl), new FrameworkPropertyMetadata(typeof(OuterControl)));
}
public ObservableCollection<string> OuterItems
{
get { return (ObservableCollection<string>)GetValue(OuterItemsProperty); }
set { SetValue(OuterItemsProperty, value); }
}
public static DependencyProperty OuterItemsProperty =
DependencyProperty.Register("OuterItems",
typeof(ObservableCollection<string>),
typeof(OuterControl),
new PropertyMetadata(new ObservableCollection<string>()));
}
}
InnerControl
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace TemplateBindingTest.Controls
{
public class InnerControl : UserControl
{
static InnerControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(InnerControl), new FrameworkPropertyMetadata(typeof(InnerControl)));
}
public ObservableCollection<string> InnerItems
{
get { return (ObservableCollection<string>)GetValue(InnerItemsProperty); }
set { SetValue(InnerItemsProperty, value); }
}
public static DependencyProperty InnerItemsProperty =
DependencyProperty.Register("InnerItems",
typeof(ObservableCollection<string>),
typeof(InnerControl),
new PropertyMetadata(new ObservableCollection<string>()));
}
}
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:TemplateBindingTest.Controls">
<Style TargetType="{x:Type controls:OuterControl}">
<Setter Property="Padding" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:OuterControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" Grid.Column="0"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
<ItemsControl Grid.Row="1" ItemsSource="{TemplateBinding OuterItems}" />
<Border Grid.Row="2" BorderThickness="1" BorderBrush="Red">
<controls:InnerControl Grid.Column="0"
InnerItems="{TemplateBinding OuterItems}">Inner Control</controls:InnerControl>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type controls:InnerControl}">
<Setter Property="Padding" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:InnerControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" Grid.Column="0"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
<ItemsControl Grid.Row="1" ItemsSource="{TemplateBinding InnerItems}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Window x:Class="TemplateBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:TemplateBindingTest.Controls"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:OuterControl OuterItems="{Binding OuterItems}">Outer Control</controls:OuterControl>
<Button Grid.Row="1" Content="Add" Click="Button_Click" HorizontalAlignment="Left" />
</Grid>
</Window>
MainWindow.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace TemplateBindingTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<string> _OuterItems;
public MainWindow()
{
InitializeComponent();
DataContext = this;
_OuterItems = new ObservableCollection<string>(new List<string>()
{
"Test 1",
"Test 2",
"Test 3",
});
}
public ObservableCollection<string> OuterItems
{
get
{
return _OuterItems;
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_OuterItems.Add(System.IO.Path.GetRandomFileName());
}
}
}

Multiple selection in WPF MVVM ListBox

I have a ListBox containing filenames. Now I need to get array of selected items from this ListBox. I found a few answers here, but none of them worked for me. I'am using Caliburn Micro framework.
Here is my View:
<Window x:Class="ProgramsAndUpdatesDesktop.Views.DeleteHistoryView"
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:ProgramsAndUpdatesDesktop.Views"
mc:Ignorable="d"
ResizeMode="CanResizeWithGrip"
MaxWidth="300"
MinWidth="300"
MinHeight="500"
Title="DeleteHistoryView" Height="500" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<ListBox x:Name="DeleteHistoryListBox" SelectedItem="{Binding Path=DeleteHistorySelectedItem}"
ItemsSource="{Binding DeleteHistoryListBox, NotifyOnSourceUpdated=True}"
SelectionMode="Multiple">
</ListBox>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1">
<Button x:Name="DeleteHistoryButtonAction">Delete</Button>
</StackPanel>
</Grid>
And here is my ViewModel:
class DeleteHistoryViewModel : Screen
{
string historyFolderPath = Environment.ExpandEnvironmentVariables(ConfigurationManager.AppSettings["HistoryFolderPath"]);
private ObservableCollection<string> deleteHistoryListBox = new ObservableCollection<string>();
public ObservableCollection<string> DeleteHistoryListBox
{
get { return deleteHistoryListBox; }
set { deleteHistoryListBox = value; NotifyOfPropertyChange(() => DeleteHistoryListBox); }
}
private List<string> deleteHistorySelectedItem = new List<string>();
public List<string> DeleteHistorySelectedItem
{
get { return deleteHistorySelectedItem; }
set { deleteHistorySelectedItem = value; }
}
public DeleteHistoryViewModel()
{
base.DisplayName = "Delete History";
}
protected override void OnInitialize()
{
FillListBox();
}
private void FillListBox()
{
string[] directory = Directory.GetFiles($"{historyFolderPath}\\", "*.json");
foreach (var item in directory)
{
string fileName = System.IO.Path.GetFileName(item).ToString();
if (!DeleteHistoryListBox.Contains(fileName))
{
DeleteHistoryListBox.Add(fileName);
}
}
}
#region ACTIONS REGION
// DELETE HISTORY ACTION
public void DeleteHistoryButtonAction()
{
foreach (var item in DeleteHistorySelectedItem)
{
MessageBox.Show(item);
}
}
#endregion
}
You can use this code for MVVM Pattern
XAML
<ListBox x:Name="DeleteHistoryListBoxItem" SelectedItem="{Binding Path=DeleteHistorySelectedItem,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding DeleteHistoryListBox, NotifyOnSourceUpdated=True}" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ViewModel
private ObservableCollection<HistoryItems> deleteHistoryListBox = new ObservableCollection<HistoryItems>();
public ObservableCollection<HistoryItems> DeleteHistoryListBox
{
get
{
return deleteHistoryListBox;
}
set
{
deleteHistoryListBox = value;
this.RaisePropertyChanged("DeleteHistoryListBox");
}
}
private HistoryItems deleteHistorySelectedItem;
public HistoryItems DeleteHistorySelectedItem
{
get
{
return deleteHistorySelectedItem;
}
set
{
var selectedItems = DeleteHistoryListBox.Where(x => x.IsSelected).Count();
this.RaisePropertyChanged("DeleteHistorySelectedItem");
}
}
Class
public class HistoryItems : INotifyPropertyChanged
{
private string item;
public string Item
{
get { return item; }
set
{
item = value;
this.RaisePropertyChanged("Item");
}
}
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
this.RaisePropertyChanged("IsSelected");
}
}
}

Custom Control Dependency Property Binding

I am going insane trying to get this to work with even the most basic example. I cannot for the life of me get binding to work. Here is a super easy example that is not working for me. I MUST be doing something incorrect.
My Custom Control in my control library assembly:
public class TestControl : Control
{
public static readonly DependencyProperty TestPropProperty =
DependencyProperty.Register("TestProp", typeof(string), typeof(TestControl), new UIPropertyMetadata(null));
public string TestProp
{
get { return (string)GetValue(TestPropProperty); }
set { SetValue(TestPropProperty, value); }
}
static TestControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl), new FrameworkPropertyMetadata(typeof(TestControl)));
}
}
And its XAML template:
<Style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBlock Text="Testing..." />
<Label Content="{Binding TestProp}" Padding="10" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the XAML consuming the control in a wpf window with a reference to my control library:
<Grid>
<ItemsControl Name="mylist">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:TestControl TestProp="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
And here's the code behind:
public partial class Test2 : Window
{
public class TestObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
private int _id;
public int id
{
get { return _id; }
set { _id = value; OnPropertyChanged("id"); }
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; OnPropertyChanged("Name"); }
}
}
public Test2()
{
InitializeComponent();
mylist.ItemsSource = new TestObject[]
{
new TestObject(){ id = 1, Name = "Tedd" },
new TestObject(){ id = 2, Name = "Fred" },
new TestObject(){ id = 3, Name = "Jim" },
new TestObject(){ id = 4, Name = "Jack" },
};
}
}
Running this example gives me four instances of the control, however I only see the "Testing..." TextBlock on each. My label is never bound. What am I misunderstanding and doing incorrectly?
You haven't set the proper binding source. You would either have to set RelativeSource:
<Label Content="{Binding TestProp, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
Or use TemplateBinding:
<Label Content="{TemplateBinding TestProp}"/>

SelectedItem of ListBox with DataTemplate

I have a ListBox:
<ListBox Name="lbsfHolder"
ItemsSource="{Binding UISupportingFunctions}"
SelectedItem="{Binding Path=SelectedSupportedFunction, Mode=TwoWay}"
SelectionMode="Multiple"
IsSynchronizedWithCurrentItem="True"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<controls:SupportingFunction GotFocus="SupportingFunction_GotFocus"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the ViewModel I have:
private SupportingFunction _selectedSupportedFunction;
public SupportingFunction SelectedSupportedFunction
{
get { return _selectedSupportedFunction; }
set
{
_selectedSupportedFunction = value;
NotifyPropertyChanged("SelectedSupportedFunction");
}
}
But when I'm trying to select any item in list box nothing happens. The SelectedItem is null for the ListBox and for SelectedValue, too. Do I need to add some special code to make this work?
UPD:
I've changed views a bit, now I have:
<UserControl x:Class="RFM.UI.WPF.Controls.SupportingFunction">
<Grid>
<ListBox Name="supportingFunctions"
ItemsSource="{Binding UISupportingFunctions}"
SelectedItem="{Binding Path=SelectedSupportedFunction, Mode=TwoWay}"
SelectionMode="Multiple"
IsSynchronizedWithCurrentItem="True"
HorizontalContentAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBox Name="tbsfName" Grid.Column="0" Text="{Binding Path=Title, Mode=TwoWay}"></TextBox>
<TextBox Name="tbsfExperssion" Grid.Column="1" Text="{Binding Path=Expression}" HorizontalAlignment="Stretch"></TextBox>
<Button Name="bsfDel" Grid.Column="2">Del</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>
In Page where this control placed:
<StackPanel Name="spSupportingFunctions">
<StackPanel Name="spsfOperations" Orientation="Horizontal">
<Button Name="bsfAdd" Width="30" Command="commands:CustomCommands.AddSupportingFunction">Add</Button>
</StackPanel>
<controls:SupportingFunction DataContext="{Binding Self}" />
</StackPanel>
at code behind of this Page
public PlotDataPage()
{
DataContext = new PlotDataViewModel();
InitializeComponent();
}
and this is the full listing of PlotDataViewModel
public class UISupportingFunction : ISupportingFunction
{
public string Title { get; set; }
public string Expression { get; set; }
}
public class PlotDataViewModel : INotifyPropertyChanged
{
public PlotDataViewModel Self
{
get
{
return this;
}
}
private ObservableCollection<UISupportingFunction> _supportingFunctions;
public ObservableCollection<UISupportingFunction> UISupportingFunctions
{
get
{
return _supportingFunctions;
}
set
{
_supportingFunctions = value;
NotifyPropertyChanged("UISupportingFunctions");
}
}
private UISupportingFunction _selectedSupportedFunction;
public UISupportingFunction SelectedSupportedFunction
{
get
{
return _selectedSupportedFunction;
}
set
{
_selectedSupportedFunction = value;
NotifyPropertyChanged("SelectedSupportedFunction");
}
}
public PlotDataViewModel()
{
UISupportingFunctions = new ObservableCollection<UISupportingFunction>();
}
public void CreateNewSupportingFunction()
{
UISupportingFunctions.Add(new UISupportingFunction() { Title = Utils.GetNextFunctionName() });
}
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I'm just calling the CreateNewSupportingFunction() method when I click Add button. Everything looks fine - the items is add and I see them. But when I'm clicking on one of the TextBoxes and then to the bsfDel button right to each item I'm getting null in SelectedSupportedFunction.
Maybe it is because of focus event have been handling by TextBox and not by ListBox?
It's either your ItemsSource UISupportingFunctions is not a SupportingFunction object or you did not set the View's Datacontext to your ViewModel.
ViewModel.xaml.cs
this.DataContext = new ViewModelClass();

Resources