Set a callback method in a DataTemplate - wpf

I have a listbox of custom items set through a DataTemplate.
<UserControl x:Name="MyMainControl">
<ListBox x:Name="lbConfigurationList"
DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
ItemsSource="{Binding ConfListVM.ObservableConfList}"
SelectionChanged="OnConfigurationSelected"
<ListBox.ItemTemplate>
<DataTemplate>
<local:EditableTextBlock Text="{Binding ConfName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Is it possible to call a method on the control owning this listbox each time someting happens in the EditableTextBlock, through something like:
<local:EditableTextBlock Text="{Binding ConfName}" MyNotifyEvent={SomeMyMainControlMethod} />
If this is possible, which terms should I search to understand how to setup and launch the event from my DataTemplate's EditableTextBlock?
Thanks in advance

Here is a full example using Behaviors:
<Window x:Class="TextBlockEventHandlerInDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TextBlockEventHandlerInDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding ., Mode=TwoWay}" >
<i:Interaction.Behaviors>
<local:ShowMessageOnTextChangedBehavior/>
</i:Interaction.Behaviors>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Codebehind, sorry i didn't have time for MVVM approach:
public partial class MainWindow : Window
{
public ObservableCollection<string> items { get; set; }
public MainWindow()
{
InitializeComponent();
items = new ObservableCollection<string>();
items.Add("item 1");
items.Add("item 2");
items.Add("item 3");
items.Add("item 4");
this.DataContext = this;
}
}
And this should be your class doing the job:
public class ShowMessageOnTextChangedBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
AssociatedObject.TextChanged += AssociatedObjectOTextChanged;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.TextChanged -= AssociatedObjectOTextChanged;
base.OnDetaching();
}
private void AssociatedObjectOTextChanged(object sender, RoutedEventArgs routedEventArgs)
{
MessageBox.Show(AssociatedObject.Text);
}
}
The other thing would be to define an attached property. I use both ways, depending on the mood :)
Ah, i was about to forget an important thing: you need to reference
Microsoft.Expression.Interactions and System.Windows.Interactivity from Expression.Blend.SDK.

Related

Binding List<string> property to Listbox WPF

Can someone help me? I have the folowing XAML code in my MainWindow.xaml file:
<ListBox ItemsSource="{Binding Files}" HorizontalAlignment="Left"
Height="371" Margin="281,53,0,0" VerticalAlignment="Top"
Width="609">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in my ViewModel.cs I have property:
public List<string> Files { get; set; }
But, when I click the button and add some items to Files nothing happens.
P.S. Sorry for my bad English :)
List does not implement INotifyCollectionChanged, instead of List, use ObservableCollection<string>
Additional Info: List vs ObservableCollection vs INotifyPropertyChanged
Here is your solution, just add this code and press 'Add String' button to make it work. I have used 'ObservableCollection' instead of List and made to listen it using 'INotifyPropertyChanged' interface in ViewModel.cs class
MainWindow.xaml
<Window x:Class="ListBox_Strings.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:ListBox_Strings"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<myApp:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Files}" HorizontalAlignment="Left"
VerticalAlignment="Top" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1" Content="Add String" Click="Button_Click"></Button>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace ListBox_Strings
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as ViewModel;
vm.Files.Add("New String");
}
}
}
ViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace ListBox_Strings
{
public class ViewModel:INotifyPropertyChanged
{
private ObservableCollection<string> m_Files;
public ObservableCollection<string> Files
{
get { return m_Files; }
set { m_Files = value;
OnPropertyChanged("Files"); }
}
public ViewModel()
{
Files = new ObservableCollection<string>();
Files.Add("A");
Files.Add("B");
Files.Add("C");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}

How to tie a some controls on one view model

I have a view model with some fields, i.e.
type ViewModel =
member x.a = [1;2;3]
member x.b = [4;5;6]
member x.c = [7]
and in WPF application places some views, as:
<Control.Resources>
<DataTemplate x:Key="ItemTempl">
<TextBlock Text="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="SomeTempl">
<ListBox ItemsSource="{Binding}"
ItemTemplate="{StaticResource ItemTempl}" />
</DataTemplate>
</Control.Resources>
<StackPanel>
<TabControl x:Name="ListBoxViewPresenter">
<TabItem Header="vm.a" Content="{Binding vm.a}"
ContentTemplate="{StaticResource SomeTempl}"/>
<TabItem Header="vm.b" Content="{Binding vm.b}"
ContentTemplate="{StaticResource SomeTempl}"/>
<TabItem Header="vm.c" Content="{Binding vm.c}"
ContentTemplate="{StaticResource SomeTempl}"/>
</TabControl>
<ListBox x:Name="ListBoxViewPresenter">
<ListBoxItem Content="{Binding vm.a}"
ContentTemplate="{StaticResource SomeTempl}" />
<ListBoxItem Content="{Binding vm.b}"
ContentTemplate="{StaticResource SomeTempl}" />
<ListBoxItem Content="{Binding vm.c}"
ContentTemplate="{StaticResource SomeTempl}" />
</ListBox>
</StackPanel>
What should I do to achieve such behavior:
when you clicked on some element in vm.a/b/c in ListBoxViewPresenter so same element in ListBoxViewPresenter is must selected in corresponding TabItem.
UPD:
Specifically my real problem, changing from origin topic.
I have ViewModel with fields: onelines, twolines... and a field with name selected_scheme.
In xaml:
<TreeViewItem Header="{Binding Path=name}" x:Name="ProjectArea">
<TreeViewItem Header="Однониточные планы" Style="{StaticResource MyTreeViewItem}">
<ContentPresenter Content="{Binding onelines}" ContentTemplate="{StaticResource SchemeWrapperTemplate}" />
</TreeViewItem>
<TreeViewItem Header="Двухниточные планы" Style="{StaticResource MyTreeViewItem}">
<ContentPresenter Content="{Binding twolines}" ContentTemplate="{StaticResource SchemeWrapperTemplate}" />
</TreeViewItem>
And data template:
<DataTemplate x:Key="SchemeWrapperTemplate">
<ListBox ItemsSource="{Binding schemes}"
ItemTemplate="{StaticResource SchemeTemplate}"
SelectedItem="{Binding selected_scheme}">
<ListBox.Style>
In other place of the program:
<Grid Grid.Row="1">
<TextBlock Text="{Binding selected_scheme.path}" />
</Grid>
And when you click on some ListBoxes then selected item not changing if you click on a yet SelectedItems.
First of all you should define the ItemsSource of your TabItem and ListBox in the ViewModel they are bound too.
Then you can bind their SelectedItem to a property in your ViewModel.
Here is a code sample (I was too lazy to create a seperate ViewModel class, hence it is mixed with my main window class, but you get the idea...) :
Codebehind :
namespace WpfApplication13
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private object _currentlySelectedItem;
public object CurrentlySelectedItem
{
get { return _currentlySelectedItem; }
set
{
_currentlySelectedItem = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("CurrentlySelectedItem"));
}
}
}
public class MyClass
{
public string MyString { get; set; }
public MyClass(string myString)
{
this.MyString = myString;
}
}
private List<MyClass> _myItemsSource = new List<MyClass>
{
new MyClass("toto"),
new MyClass("tata")
};
public List<MyClass> MyItemsSource
{
get { return _myItemsSource; }
set { _myItemsSource = value; }
}
public object A
{
get { return "toto"; }
}
public object B
{
get { return "tata"; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
}
xaml :
<Window x:Class="WpfApplication13.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TabControl x:Name="ListBoxViewPresenter"
SelectedItem="{Binding CurrentlySelectedItem}"
ItemsSource="{Binding MyItemsSource}" />
<ListBox x:Name="ListBoxViewPresenter2"
SelectedItem="{Binding CurrentlySelectedItem}"
ItemsSource="{Binding MyItemsSource}" />
</StackPanel>
</Grid>
</Window>
New answer after you edited :
It does not make much sense to bind the same object to the SelectedItem property of two different lists that contain different elements. You have to redesign your application or you may be confronted to many problems due to this strange design in the futur.
Still, you can achieve want you want to do with a little codebehing. When the user clicks on one of your ListBox, you set the selected item of your other listbox to null. Then you will be notified when you re-click on the first listbox since the selection goes from null to something.
xaml:
<Window x:Class="WpfApplication13.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ListBox x:Name="ListBoxViewPresenter"
SelectedItem="{Binding CurrentlySelectedItem}"
ItemsSource="{Binding MyItemsSource}" />
<ListBox x:Name="ListBoxViewPresenter2"
SelectedItem="{Binding CurrentlySelectedItem}"
ItemsSource="{Binding MyItemsSource2}" />
</StackPanel>
</Grid>
</Window>
codebehind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.ComponentModel;
namespace WpfApplication13
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private object _currentlySelectedItem;
public object CurrentlySelectedItem
{
get { return _currentlySelectedItem; }
set
{
_currentlySelectedItem = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("CurrentlySelectedItem"));
}
}
}
private List<int> _myItemsSource = new List<int> { 1, 2 };
private List<int> _myItemsSource2 = new List<int> { 3, 4 };
public List<int> MyItemsSource
{
get { return _myItemsSource; }
set { _myItemsSource = value; }
}
public List<int> MyItemsSource2
{
get { return _myItemsSource2; }
set { _myItemsSource2 = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ListBoxViewPresenter.PreviewMouseDown += ListBoxViewPresenter_PreviewMouseDown;
ListBoxViewPresenter2.PreviewMouseDown += ListBoxViewPresenter2_PreviewMouseDown;
}
void ListBoxViewPresenter_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
ListBoxViewPresenter2.SelectedItem = null;
}
void ListBoxViewPresenter2_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
ListBoxViewPresenter.SelectedItem = null;
}
}
}

How can I bind a List as ItemSource to ListView in XAML?

I'm learning WPF and would like to have a collection similar to a LinkedList, to where I can add and remove strings. And I want to have a ListView that listen to that collection with databinding. How can I do bind a simple list collection to a ListView in XAML?
My idea (not working) is something like this:
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<LinkedList x:Key="myList"></LinkedList> //Wrong
<Window.Resources>
<Grid>
<ListView Height="100" HorizontalAlignment="Left" Margin="88,134,0,0"
Name="listView1" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource myList}}"/> //Wrong
</Grid>
</Window>
All my code (updated version, not working):
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0"
Name="textBox1" VerticalAlignment="Top" Width="120" />
<Button Content="Button" Height="23" HorizontalAlignment="Right"
Margin="0,12,290,0" Name="button1" VerticalAlignment="Top" Width="75"
Click="button1_Click" />
<ListView Height="100" HorizontalAlignment="Left" Margin="88,134,0,0"
Name="listView1" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding myList}"/>
</Grid>
</Window>
C#-code:
namespace TestApp
{
public partial class MainWindow : Window
{
ObservableCollection<string> myList = new ObservableCollection<string>();
public MainWindow()
{
InitializeComponent();
myList.Add("first string");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
myList.Add(textBox1.Text);
textBox1.Text = myList.Count+"st";
}
}
}
The approach selected as an answer works fine... but I don't specifically like having to specify the DataContext programmatically while setting everything else in XAML, I don't feel like it's "proper" (maybe this is just me). So for the next person, or anyone else who thinks like me and finds this off a search engine (like i did), here is the way to do it in XAML:
C#
public sealed partial class MainPage : Page
{
public ObservableCollection<string> Messages { get; set; }
public MainPage()
{
this.Messages = new ObservableCollection<string>();
this.InitializeComponent();
}
}
XAML
<Window
....
DataContext="{Binding RelativeSource={RelativeSource Self}}"
...>
<ListView ItemsSource="{Binding Messages}" ... />
</Window>
To be honest I think {Binding RelativeSource={RelativeSource Self}} should be the default value any top level element's (Page, Window, etc...) DataConext because it is simply how a lot of people expect it to work, I know it's how I assume it would work. Honestly, I feel like {Binding RelativeSource={RelativeSource Self}} is a bit verbose and almost long for a shorter syntax.
You can only databind to public properties and you need to set the DataContext.
public partial class MainWindow : Window
{
public ObservableCollection<string> myList { get; private set; }
public MainWindow()
{
InitializeComponent();
myList = new ObservableCollection<string>();
myList.Add("first string");
DataContext = this;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
myList.Add(textBox1.Text);
textBox1.Text = myList.Count + "st";
}
}

How to bind a WPF control to the code behind?

I have this XAML:
<Window x:Class="WpfBindToCodeBehind.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Loaded="Window_Loaded">
<StackPanel Orientation="Vertical">
<Button Name="ToggleExpand" Click="ToggleExpand_Click">Toggle Expander</Button>
<Expander Name="Expander"
Header="Don't click me, click the button!"
IsExpanded="{Binding RelativeSource={RelativeSource Self},Path=MayExpand}">
<TextBlock Text="{Binding}"/>
</Expander>
</StackPanel>
</Window>
This is the code behind:
public partial class Window1 : Window,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Window1()
{
InitializeComponent();
}
private void ToggleExpand_Click(object sender, RoutedEventArgs e)
{
MayExpand = !mayExpand;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Expander.DataContext = "Show me";
}
private bool mayExpand;
public bool MayExpand
{
get { return mayExpand; }
set
{
mayExpand = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MayExpand"));
}
}
}
The Binding expression for the IsExpanded property is not working. This code is a simplification, in reality the expander's binding is already set through the datacontent of a parent control.
How can I bind the IsExpanded property to a property of the code behind?
Can I bind it to the result of a method in the code behind?
The source for the binding is a RelativeSource.Self. That means the source is the Expander rather than the Window. Something like this will work:
IsExpanded="{Binding MayExpand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}"
You could also use a name to simplify things:
<Window x:Name="_root">
<Expander IsExpanded="{Binding MayExpand, ElementName=_root}"/>

Getting events from the WPF Checked ComboBox

Near total WPF noob. So I hooked up a combobox to have checkboxes using the following item template:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Checked="{Binding IsSelected}"
Width="20" Name="chkDayName" Click="chkDayName_Click"/>
<TextBlock Text="{Binding DayOfWeek}"
Width="100" Name="txtDayName" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
On the actual event of a person clicking a checkbox, i catch the event in chkDayName_Click method. I have the following questions:
How do I find out values of the corresponding TextBlock in the item template?
How do i find out the index of the item that was clicked?
Is there a way to get to the parent?
Thanks.
If I understand it you want to know which combobox items are checked? You can use the chkDayName_Click for that and add the name of the day as Tag of the CheckBox. This feels very Winforms. In WPF you normally let your databinding handle functionality like this. Below is some code that will display selected item in a textbox and a list of checked weekdays.
XAML:
<Window x:Class="DayComboBoxDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<CollectionViewSource x:Key="checkedWeekdays" Source="{Binding Path=WeekDays}" Filter="IsCheckedFilter" />
</Window.Resources>
<StackPanel>
<ComboBox
ItemsSource="{Binding Path=WeekDays}"
SelectedItem="{Binding Path=SelectedWeekDay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
IsChecked="{Binding Path=IsChecked}"
Width="20" Click="chkDayName_Click"/>
<TextBlock
Text="{Binding DayOfWeek}" Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="{Binding Path=SelectedWeekDay.DayOfWeek}" />
<ListBox
DisplayMemberPath="DayOfWeek"
ItemsSource="{Binding Source={StaticResource checkedWeekdays}}" />
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace DayComboBoxDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<WeekDay> weekDays = new List<WeekDay>();
foreach (DayOfWeek dayOfWeek in System.Enum.GetValues(typeof(DayOfWeek)))
{
weekDays.Add(new WeekDay() { DayOfWeek = dayOfWeek });
}
WeekDays = weekDays;
_checkedWeekdays = FindResource("checkedWeekdays") as CollectionViewSource;
DataContext = this;
}
public IEnumerable<WeekDay> WeekDays { get; set; }
public WeekDay SelectedWeekDay
{
get { return (WeekDay)GetValue(SelectedWeekDayProperty); }
set { SetValue(SelectedWeekDayProperty, value); }
}
public static readonly DependencyProperty SelectedWeekDayProperty =
DependencyProperty.Register("SelectedWeekDay",
typeof(WeekDay),
typeof(Window1),
new UIPropertyMetadata(null));
private void chkDayName_Click(object sender, RoutedEventArgs e)
{
_checkedWeekdays.View.Refresh();
}
private void IsCheckedFilter(object sender, FilterEventArgs e)
{
WeekDay weekDay = e.Item as WeekDay;
e.Accepted = weekDay.IsChecked;
}
private CollectionViewSource _checkedWeekdays;
}
public class WeekDay
{
public DayOfWeek DayOfWeek { get; set; }
public bool IsChecked { get; set; }
}
}
You can try ComboBox's SelectedIndex or SelectedValue to tell the SelectedItem. In the MVVM fashion, you can have a two-way binding between SelectedIndex and one of you ViewModel properties.

Resources