Usercontrol Binding inside Itemscontrol [duplicate] - c#

This question already has answers here:
DependencyProperty not triggered
(2 answers)
Closed 5 days ago.
This post was edited and submitted for review 5 days ago.
Edit2 if anyone is interested.
This is how i fixed the Problem (sadly not really mvvm but the binding did not work somehow if the datagrid was inside an itemscontrol)
public ObservableCollection<int> SelectedCells
{
get { return (ObservableCollection<int>)GetValue(SelectedCellsProperty); }
set { SetValue(SelectedCellsProperty, value); }
}
public static readonly DependencyProperty SelectedCellsProperty = DependencyProperty.Register(
"SelectedCells",
typeof(ObservableCollection<int>),
typeof(DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedCellsChanged));
private static void SelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as DataGrid;
control?.RaiseSelectedCellsChangedEvent(e.OldValue, e.NewValue);
}
public event RoutedPropertyChangedEventHandler<object> SelectedCellsChangedEvent;
public void RaiseSelectedCellsChangedEvent(object oldValue, object newValue)
{
SelectedCellsChangedEvent?.Invoke(this, new RoutedPropertyChangedEventArgs<object>(oldValue, newValue));
}
In The mainView:
<uc:DataGrid Height="160" Width="300"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataGridSource="{Binding}"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
SelectedCellsChangedEvent="DataGrid_SelectedCellsChangedEvent"
code behind mainwindow:
private void DataGrid_SelectedCellsChangedEvent(object sender, RoutedPropertyChangedEventArgs<object> e)
{
ObservableCollection<int> newValue = e.NewValue as ObservableCollection<int>;
}
Edit:
The PropertyChangedCallback Function is getting called, but the problem is that it is only not working if the binding is inside my itemscontrol.
If the <uc:datagrid> is not inside an itemscontrol it is working!
public ObservableCollection<int> SelectedCells
{
get { return (ObservableCollection<int>)GetValue(SelectedCellsProperty); }
set { SetValue(SelectedCellsProperty, value); }
}
public static readonly DependencyProperty SelectedCellsProperty =
DependencyProperty.Register("SelectedCells", typeof(ObservableCollection<int>), typeof(DataGrid), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedCellsChanged)));
private static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This method will be called whenever the value of the SelectedCells property changes
// You can perform any additional logic you need to here
Console.WriteLine("change"); <-- This is getting called, also if in my itemscontrol
}
I have a problem with the binding inside of an Itemscontrol.
I want to know which cells are currently selected in my Datagrid(inside a usercontrol).
This is my Datagrid Usercontrol.
Here I added a Dependency Property SelectedCells.
/// <summary>
/// Interaction logic for DataGrid.xaml
/// </summary>
public partial class DataGrid : UserControl
{
public DataGrid()
{
InitializeComponent();
SelectedCells = new ObservableCollection<int>();
}
public DataGridValue DataGridSource
{
get { return (DataGridValue)GetValue(DataGridSourceProperty); }
set { SetValue(DataGridSourceProperty, value); }
}
public static readonly DependencyProperty DataGridSourceProperty =
DependencyProperty.Register("DataGridSource", typeof(DataGridValue), typeof(DataGrid), new PropertyMetadata(null));
public ObservableCollection<int> SelectedCells
{
get { return (ObservableCollection<int>)GetValue(SelectedCellsProperty); }
set { SetValue(SelectedCellsProperty, value); }
}
public static readonly DependencyProperty SelectedCellsProperty =
DependencyProperty.Register("SelectedCells", typeof(ObservableCollection<int>), typeof(DataGrid), new PropertyMetadata(null));
private void datagrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
SelectedCells = new ObservableCollection<int>();
foreach (DataGridCellInfo cellInfo in datagrid.SelectedCells)
{
int columnIndex = cellInfo.Column.DisplayIndex;
int rowIndex = datagrid.ItemContainerGenerator.IndexFromContainer(
datagrid.ItemContainerGenerator.ContainerFromItem(cellInfo.Item));
//Console.WriteLine($"Cell ({rowIndex}, {columnIndex}) is selected.");
switch((DayName)columnIndex)
{
case 0: break;
case DayName.Mo:
if (DataGridSource.DataGridList[rowIndex].MondayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].MondayCell.Text));
}
break;
case DayName.Di:
if (DataGridSource.DataGridList[rowIndex].TuesdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].TuesdayCell.Text));
}
break;
case DayName.Mi:
if (DataGridSource.DataGridList[rowIndex].WednesdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].WednesdayCell.Text));
}
break;
case DayName.Do:
if (DataGridSource.DataGridList[rowIndex].ThursdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].ThursdayCell.Text));
}
break;
case DayName.Fr:
if (DataGridSource.DataGridList[rowIndex].FridayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].FridayCell.Text));
}
break;
case DayName.Sa:
if (DataGridSource.DataGridList[rowIndex].SaturdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].SaturdayCell.Text));
}
break;
case DayName.So:
if (DataGridSource.DataGridList[rowIndex].SundayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].SundayCell.Text));
}
break;
}
}
}
}
<DataGrid x:Name="datagrid"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeColumns="False"
CanUserReorderColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
AutoGenerateColumns="False"
SelectionUnit="Cell"
SelectionMode="Extended"
HeadersVisibility="Column"
Background="Transparent"
BorderBrush="Transparent"
SelectedCellsChanged="datagrid_SelectedCellsChanged"
ItemsSource="{Binding DataGridSource.DataGridList, RelativeSource={RelativeSource AncestorType=UserControl}}"
>
If I just add my DataGrid to my mainView the binding is working:
<uc:DataGrid Height="160" Width="300"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataGridSource="{Binding TestGrid}"
SelectedCells="{Binding TestList, Mode=TwoWay}"
Margin="20,20,0,0"/>
property:
public partial class MainViewModel : BaseViewModel
{
private ObservableCollection<int> testList;
public ObservableCollection<int> TestList
{
get
{
if (testList == null)
{
testList = new ObservableCollection<int>();
}
return testList;
}
set
{
testList = value;
}
}
But if I am doing this Inside of an Itemscontrol the binding is not working.
I want it to bind to the Property inside of my MainViewModel.
In the debugger there are no binding issues, but the setter is never called.
This is my Itemscontrol:
<ItemsControl Name="MyItemsControl" Visibility="Visible"
ItemsSource="{Binding Path=DataGridSelectedYear, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Margin="0,55,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uc:DataGrid Height="160" Width="300"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataGridSource="{Binding}"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
SelectedCells="{Binding DataContext.TestList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=Window}}"
Margin="20,20,0,0">
<uc:DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Test"
Command="{Binding Path=DataContext.AddBackColorCommand}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</uc:DataGrid.ContextMenu>
</uc:DataGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But it also does not work if I add the property to my class where the Itemscontrol does get it’s data from (DataGridSelectedYear). Also no Binding errors in the debugger but the setter never gets called.

The binding engine calls the SetValue method of the DependencyObject when setting a dependency property so the setter of your SelectedCells property is not supposed to be invoked when you bind to a source property.
You could register a PropertyChangedCallback if you want to confirm that the property is set as expected.

Related

How to shift focus from an edited cell in a datagrid to a text box in WPF

I'm attempting to have a user input textbox take an ID string and fetch data to fill rows in a datagrid, then trigger editing a specific row and cell in the datagrid based on the value of the user input and then put focus back on the textbox when the return/enter key is pressed after the user enters a value in the datagrid cell.
I have most of this working correctly except for the last step, the enter key has to be pressed twice in order to return to the text box, I would like it to return on first press.
If the edited cell is selected by the user directly using the mouse then it works correctly, a single enter press sends focus back to the textbox.
The automatic cell editing is triggered with an attached property and a helper class that listens for the OnCurrentCellChanged event and triggers the datagrid BeginEdit method.
I wonder if this needs to be closed off/ended before the focus can change properly?
(Helper class implemented based on answer here, with great thanks to #Orace)
How can I achieve this? I have tried commiting the edit, cancelling the edit in the xaml code behind PreviewKeyDown method but no luck. So possibly I have to change the OnCurrentCellChanged event method somehow?
my xaml:
<Window x:Class="TestChangingEditedCellFocus.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:TestChangingEditedCellFocus" d:DataContext="{d:DesignInstance Type=local:ViewModel}"
mc:Ignorable="d"
FocusManager.FocusedElement="{Binding ElementName=idfield}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<CollectionViewSource x:Key="srModels" Source="{Binding Models}"/>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="idfield" Width="100" Height="30" Text="{Binding Path=ModelId, UpdateSourceTrigger=PropertyChanged}"/>
<Button IsDefault="True" Command="{Binding Path=Command}" Content="Get Row" Height="30"/>
</StackPanel>
<DataGrid Grid.Row="1"
x:Name="modelgrid"
local:DataGridAutoEdit.AutoEditColumn="2"
ItemsSource="{Binding Source={StaticResource srModels}}"
SelectedItem="{Binding CurrentModel, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
AutoGenerateColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow"
PreviewKeyDown="modelgrid_PreviewKeyDown"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="ID" Width="Auto" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Id}" HorizontalContentAlignment="Right"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Name}" HorizontalContentAlignment="Right"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0"
VerticalContentAlignment="Center" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Right"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Xaml code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void modelgrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if ((e.Key == Key.Enter) || (e.Key == Key.Return))
{
//DataGrid grid = sender as DataGrid;
//grid.CancelEdit();
idfield.Focus();
idfield.SelectAll();
}
}
}
The VeiwModel:
public class ViewModel : ObservableObject
{
private ObservableCollection<model> _models;
private model _currentModel;
private string _modelId;
private ICommand _command;
public ObservableCollection<model> Models
{
get
{
if (_models is null)
{
_models = new ObservableCollection<model>();
OnPropertyChanged("Models");
}
return _models;
}
}
public model CurrentModel
{
get
{
return _currentModel;
}
set
{
_currentModel = value;
OnPropertyChanged("CurrentModel");
}
}
public string ModelId
{
get
{
return _modelId;
}
set
{
_modelId = value;
OnPropertyChanged("ModelId");
}
}
public ICommand Command
{
get
{
if (_command == null)
{
_command = new RelayCommand(param=>ExcecuteCommand(), pred=> ModelId is not null);
}
return _command;
}
}
public void ExcecuteCommand()
{
Models.Clear();
if (Models.Count == 0)
{
Models.Add(new model() { Id = 1, Name = "name1" });
Models.Add(new model() { Id = 2, Name = "name2" });
Models.Add(new model() { Id = 3, Name = "name3" });
Models.Add(new model() { Id = 4, Name = "name4" });
}
foreach (model model in Models)
{
System.Diagnostics.Debug.WriteLine("Model: " + model.Name);
int id = int.Parse(ModelId);
if(id == model.Id)
{
CurrentModel = model;
}
}
ModelId = null;
}
}
The DataGridAutoEdit helper class to trigger edit mode:
class DataGridAutoEdit
{
public static readonly DependencyProperty AutoEditColumnProperty = DependencyProperty.RegisterAttached("AutoEditColumn", typeof(int), typeof(DataGridAutoEdit), new PropertyMetadata(default(int), AutoEditColumnChangedCallback));
public static void SetAutoEditColumn(DependencyObject element, int value)
{
element.SetValue(AutoEditColumnProperty, value);
}
public static int GetAutoEditColumn(DependencyObject element)
{
return (int)element.GetValue(AutoEditColumnProperty);
}
private static void AutoEditColumnChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not DataGrid dataGrid)
return;
GetAutoEditColumnHelper(dataGrid)?.Dispose();
if (e.NewValue is int columnIndex)
{
SetAutoEditColumnHelper(d, new AutoEditColumnHelper(dataGrid, columnIndex));
}
else
{
d.ClearValue(AutoEditColumnHelperProperty);
}
}
private static readonly DependencyProperty AutoEditColumnHelperProperty = DependencyProperty.RegisterAttached("AutoEditColumnHelper", typeof(AutoEditColumnHelper), typeof(DataGridAutoEdit), new PropertyMetadata(default(AutoEditColumnHelper)));
private static void SetAutoEditColumnHelper(DependencyObject element, AutoEditColumnHelper value)
{
element.SetValue(AutoEditColumnHelperProperty, value);
}
private static AutoEditColumnHelper? GetAutoEditColumnHelper(DependencyObject element)
{
return element.GetValue(AutoEditColumnHelperProperty) as AutoEditColumnHelper;
}
//add private class to get datagrid auto cel edit function working, move to helpers if this works
private class AutoEditColumnHelper : IDisposable
{
private readonly DataGrid _dataGrid;
private readonly int _columnIndex;
private object? _lastItem;
public AutoEditColumnHelper(DataGrid dataGrid, int columnIndex)
{
_dataGrid = dataGrid;
_columnIndex = columnIndex;
_dataGrid.CurrentCellChanged += OnCurrentCellChanged;
}
public void Dispose()
{
_dataGrid.CurrentCellChanged -= OnCurrentCellChanged;
}
private void OnCurrentCellChanged(object? sender, EventArgs e)
{
DataGridCellInfo currentCell = _dataGrid.CurrentCell;
if (!currentCell.IsValid || Equals(currentCell.Item, _lastItem))
{
return;
}
DataGridColumn autoEditColumn = GetAutoEditColumn();
if (autoEditColumn is null)
{
return;
}
_dataGrid.Dispatcher.BeginInvoke(() =>
{
_lastItem = _dataGrid.SelectedItem;
_dataGrid.CurrentCell = new DataGridCellInfo(_lastItem, autoEditColumn);
_dataGrid.BeginEdit();
});
}
private DataGridColumn? GetAutoEditColumn()
{
return _columnIndex < 0 || _columnIndex > _dataGrid.Columns.Count ? null : _dataGrid.Columns[_columnIndex];
}
}
}
A working simplified project is here.
After some trial and error I found editing my PreviewKeyDowan method in code behind to both Commit and Cancel the edit AND using SendKeys to send the Enter command again worked to send the cursor back to the textbox feild as I wanted:
private void modelgrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if ((e.Key == Key.Enter) || (e.Key == Key.Return))
{
DataGrid grid = sender as DataGrid;
grid.CommitEdit();
grid.CancelEdit();
SendKeys.SendWait("{ENTER}");
idfield.Focus();
}
}
but this seems pretty hacky and I'll be happy for any other suggestions.

wpf mvvm set focus particular cell of datagrid

I'm using mvvm -
I have been Googling this for a few hours, and I can't seem to find an example of how to set the focus to a cell and place it in edit mode.
If I have a datagrid and I want set focus in a determinate cell from viewmodel.
how can I do?
From your general question I think this might solve the problem:
grid.Focus();
grid.CurrentCell = new DataGridCellInfo(grid.Items[rowIndex],grid.Columns[columnIndex]);
Edit
for MVVM pattern it would be like this:
public MainWindow()
{
InitializeComponent();
DataContext = this;
for (int i = 0; i < 10; i++)
{
Items.Add(new VM() { Text1=i.ToString(), Text2 = (i+100).ToString()});
}
FocusCommand = new MyCommand(o =>
{
var dg = o as DataGrid;
if (dg != null) {
dg.Focus();
FocusedCell = new DataGridCellInfo(
dg.Items[FocusedRowIndex], dg.Columns[FocusedColumnIndex]);
}
});
}
//Items Observable Collection
public ObservableCollection<VM> Items { get { return _myProperty; } }
private ObservableCollection<VM> _myProperty = new ObservableCollection<VM>();
//FocusedCell Dependency Property
public DataGridCellInfo FocusedCell
{
get { return (DataGridCellInfo)GetValue(FocusedCellProperty); }
set { SetValue(FocusedCellProperty, value); }
}
public static readonly DependencyProperty FocusedCellProperty =
DependencyProperty.Register("FocusedCell", typeof(DataGridCellInfo), typeof(MainWindow), new UIPropertyMetadata(null));
//FocusCommand Dependency Property
public MyCommand FocusCommand
{
get { return (MyCommand)GetValue(FocusCommandProperty); }
set { SetValue(FocusCommandProperty, value); }
}
public static readonly DependencyProperty FocusCommandProperty =
DependencyProperty.Register("FocusCommand", typeof(MyCommand), typeof(MainWindow), new UIPropertyMetadata(null));
//FocusedRowIndex Dependency Property
public int FocusedRowIndex
{
get { return (int)GetValue(FocusedRowIndexProperty); }
set { SetValue(FocusedRowIndexProperty, value); }
}
public static readonly DependencyProperty FocusedRowIndexProperty =
DependencyProperty.Register("FocusedRowIndex", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));
//FocusedColumnIndex Dependency Property
public int FocusedColumnIndex
{
get { return (int)GetValue(FocusedColumnIndexProperty); }
set { SetValue(FocusedColumnIndexProperty, value); }
}
public static readonly DependencyProperty FocusedColumnIndexProperty =
DependencyProperty.Register("FocusedColumnIndex", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));
XAML:
<StackPanel Width="100">
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False"
x:Name="datagrid"
CurrentCell="{Binding FocusedCell}">
<DataGrid.Columns>
<DataGridTextColumn Header="col1" Binding="{Binding Text1}"/>
<DataGridTextColumn Header="col2" Binding="{Binding Text2}"/>
</DataGrid.Columns>
</DataGrid>
<TextBox Text="{Binding FocusedRowIndex}" Margin="10"/>
<TextBox Text="{Binding FocusedColumnIndex}" Margin="10"/>
<Button Command="{Binding FocusCommand}"
CommandParameter="{Binding ElementName=datagrid}" Content="focus"/>
</StackPanel>
Now if you type the desired row index in first textbox and the desired column index in second one, and then click on focus button, it should focus on a cell.
to make sure it's working, after clicking focus, start typing something.

How to set SeletedItems = null in a Listbox if i select a other Listbox

basically i'm trying to do THIS
but you can see it is not MVVM so i'm looking for a way to set SeletedItems = null or clear() depending on what's doable
because in my View i will got N ListBoxes and if he pressed a Button after selecting some Items i will change some properties of the SeletedItems but only for the last active Listbox
so i decided to use on SelectedItems Property for all the Listboxes but it doesn't work based on 2 problems i can't bind to to SelectedItems and based on this i can't test how to remove the selection from the other Listboxes
EDIT:
to give you an simple example:
XAML
<Window x:Class="Test.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">
<StackPanel>
<ListBox Width="432" Height="67"
HorizontalAlignment="Left" VerticalAlignment="Top"
SelectionMode="Extended"
<!-- SeletedItems="{Binding SelectedListItems}" ??? -->
ItemsSource="{Binding Collection1}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding MyText}"
Background="{Binding MyBackground}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Width="432" Height="67"
HorizontalAlignment="Left" VerticalAlignment="Top"
SelectionMode="Extended"
<!-- SeletedItems="{Binding SelectedListItems}" ??? -->
ItemsSource="{Binding Collection2}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding MyText}"
Background="{Binding MyBackground}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="unselect" Width="80" Height="150"
HorizontalAlignment="Right" VerticalAlignment="Top"
Command="{Binding MyCommand}"/>
</StackPanel>
</Window>
Code
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace Test
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM : INotifyPropertyChanged
{
private ObservableCollection<DetailVM> _SelectedListItems = new ObservableCollection<DetailVM>();
public ObservableCollection<DetailVM> SelectedListItems
{
get { return _SelectedListItems; }
set
{
_SelectedListItems = value;
OnPropertyChanged("SelectedListItems");
}
}
public List<DetailVM> Collection1 { get; set; }
public List<DetailVM> Collection2 { get; set; }
private RelayCommand _myCommand;
public ICommand MyCommand
{
get { return _myCommand?? (_myCommand= new RelayCommand(param => OnMyCommand())); }
}
public void OnMyCommand()
{
foreach DetailVM item in SelectedListItems
{
item.MyBackground ="Red";
}
}
public VM()
{
Collection1 = new List<DetailVM>();
Collection2 = new List<DetailVM>();
for (int i = 0; i < 10; i++)
{
Collection1.Add(new DetailVM { MyText = "C1ITEM " + i });
Collection2.Add(new DetailVM { MyText = "C2ITEM " + i });
}
}
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class DetailVM
{
public string MyText { get; set; }
public string MyBackground { get; set; }
}
}
The code above should change the color of the Textbox background to Red
if the user selected some Items in a Listbox and he should only be able to seleted Items in one Listbox at the same time
so how to do this? (bear in mind this is a simple example but i need this for N Listboxes which will be generated over a template)
First of all, I would recommend you to extend ListView so that it includes a bindable SelectedValues property (you cannot use the name SelectedItems since it's already a non-bindable property of ListView). Here's an example of how this can be achieved.
public class MultiSelectListView : ListView
{
// Using a DependencyProperty as backing store
public static readonly DependencyProperty SelectedValuesProperty =
DependencyProperty.Register("SelectedValues", typeof(IList), typeof(MultiSelectListView), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
public IList SelectedValues
{
get { return (IList)GetValue(SelectedValuesProperty); }
set { SetValue(SelectedValuesProperty, value); }
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// if selected items list implements INotifyCollectionChanged, we subscribe to its CollectionChanged event
var element = (MultiSelectListView)d;
if (e.OldValue != null && e.OldValue is INotifyCollectionChanged)
{
var list = e.OldValue as INotifyCollectionChanged;
list.CollectionChanged -= element.OnCollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged)
{
var list = e.NewValue as INotifyCollectionChanged;
list.CollectionChanged += element.OnCollectionChanged;
}
}
// when selection changes in the view, elements are added or removed from the underlying list
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (SelectedValues != null)
{
foreach (var item in e.AddedItems)
{
if (!SelectedValues.Contains(item))
SelectedValues.Add(item);
}
foreach (var item in e.RemovedItems)
{
if (SelectedValues.Contains(item))
SelectedValues.Remove(item);
}
}
base.OnSelectionChanged(e);
}
// when underlying list changes, we set the control's selected items to the contents of the list
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (SelectedValues != null)
{
SetSelectedItems(SelectedValues);
}
}
}
Once you've done this you can control the behavior of a list's selected items through the viewmodel. Clearing the viewmodel list clears the selected items in the control.
Next you can subscribe to the collection changed event of your selected items lists (in the view model) and in the handler check whether you need to clear any of your lists.

Changing DataContext doesn't update list fields

I created a new TextBlock class which has ItemsSource property and translates that ItemsSource into "Run" object:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class ImageOption : ISection
{
private InlineUIContainer mContainer;
public ImageOption(string aDisplay)
{
Image lImage;
lImage = new Image();
lImage.Source = new BitmapImage(new Uri(Environment.CurrentDirectory + aDisplay));
lImage.Height = 15;
lImage.Width = 15;
mContainer = new InlineUIContainer(lImage);
}
public Inline GetDisplayElement()
{
return mContainer;
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableCollection<ISection>), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata(new ObservableCollection<ISection>(),
new PropertyChangedCallback(SetContent)));
public ObservableCollection<ISection> ItemsSource
{
get
{
return GetValue(ItemsSourceProperty) as ObservableCollection<ISection>;
}
set
{
if (ItemsSource != null)
ItemsSource.CollectionChanged -= CollectionChanged;
SetValue(ItemsSourceProperty, value);
SetContent();
ItemsSource.CollectionChanged += CollectionChanged;
}
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
SetContent();
}
private static void SetContent(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DependencyObject lParent = d;
MultiTypeDynamicTextBlock lPanel = lParent as MultiTypeDynamicTextBlock;
if (lPanel != null)
{
lPanel.ItemsSource = e.NewValue as ObservableCollection<ISection>;
}
}
private void SetContent()
{
if (ItemsSource != null)
{
Inlines.Clear();
foreach (ISection lCurr in ItemsSource)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
If I Bind the ItemsSource directly to the DataContext, it works.
But if I bind it to an object that changes at runtime (such as SelectedItem on a ListBox) it doesn't update the text when a new item is selected.
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding Items}"/>
</StackPanel>
</StackPanel>
Any reason why?
In your example, does the SelectedItem has two properties Title and Items? Or is Items a property in your viewmodel? If the answer is the latter, than you can find a solution below.
I don't entirely understand what you mean, but I'll give it a try.
If you mean that the ItemsSource on your custom control isn't set, than you have to point XAML into the right direction.
Below you can find a solution, if this is what you want to achieve.
What I did is pointing the compiler to the right source with this line of code:
ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"
Here you say that the compiler can find the Binding property in the DataContext of the Window (or any control where you can find the property).
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"/>
</StackPanel>
</StackPanel>
Hopefully this helped.
EDIT
The title property will changes when I select another one from the ListBox.
If Items is set to a new ObservableCollection, do you call the OnPropertyChanged event for Items when the SelectedItem changes?
OnPropertyChanged("Items");
Thank you for your help.
I managed to fix this by updating the MultiTypeDynamicTextBlock as follows:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
ISection Clone();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
public ISection Clone()
{
return new TextOption(mText.Text);
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class SectionList
{
private ObservableCollection<ISection> mList;
public Action CollectionChanged;
public ObservableCollection<ISection> Items
{
get
{
ObservableCollection<ISection> lRet = new ObservableCollection<ISection>();
foreach (ISection lCurr in mList)
{
lRet.Add(lCurr.Clone());
}
return lRet;
}
}
public int Count { get { return mList.Count; } }
public SectionList()
{
mList = new ObservableCollection<ISection>();
}
public void Add(ISection aValue)
{
mList.Add(aValue);
}
public SectionList Clone()
{
SectionList lRet = new SectionList();
lRet.mList = Items;
return lRet;
}
}
public MultiTypeDynamicTextBlock()
{
}
public static readonly DependencyProperty ItemsCollectionProperty =
DependencyProperty.Register("ItemsCollection", typeof(SectionList), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata((PropertyChangedCallback)((sender, args) =>
{
MultiTypeDynamicTextBlock textBlock = sender as MultiTypeDynamicTextBlock;
SectionList inlines = args.NewValue as SectionList;
if (textBlock != null)
{
if ((inlines != null) && (inlines.Count > 0))
{
textBlock.ItemsCollection.CollectionChanged += textBlock.ResetInlines;
textBlock.Inlines.Clear();
foreach (ISection lCurr in textBlock.ItemsCollection.Items)
{
textBlock.Inlines.Add(lCurr.GetDisplayElement());
}
}
else
{
inlines = new SectionList();
inlines.Add(new TextOption("No value set"));
textBlock.ItemsCollection = inlines;
}
}
})));
public SectionList ItemsCollection
{
get
{
return (SectionList)GetValue(ItemsCollectionProperty);
}
set
{
SectionList lTemp;
if (value == null)
{
lTemp = new SectionList();
lTemp.Add(new TextOption("No value set for property"));
}
else
{
lTemp = value;
}
SetValue(ItemsCollectionProperty, lTemp);
}
}
private void ResetInlines()
{
Inlines.Clear();
foreach (ISection lCurr in ItemsCollection.Items)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
And I update the fields that were Binded to be of type MultiTypeDynamicTextBlock.SectionList
As long as I am using a copy (Clone) it is working, for some reason when I don't clone it removes the value from the display in the list, if someone knows why I would love to learn but I managed to go around it.
the XAML of the window is:
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding GeneralItems}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsCollection="{Binding Items}" Margin="20,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding GeneralItems, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock DataContext="{Binding Items}" ItemsCollection="{Binding}" Margin="20,0,0,0"/>
</StackPanel>
</StackPanel>

How do I bind a custom UserControl to the current item inside a ItemTemplate?

I have a ListBox that is data bound to an ObservableCollection.
Inside the DataTemplate, I have this custom user control that I want to bind to the current item.
How do I do that?
What should the binding path be?
Model:
private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items
{
get { return _items; }
set
{
if (_items.Equals(value))
return;
_items = value;
RaisePropertyChanged("Items");
}
}
XAML:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="{Binding Id}" Margin="2"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}" Margin="2"/>
<uc:MyControl MyValue="{Binding <WHATSHOULDTHISBE>, Mode=TwoWay}" Margin="2"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
User control:
public partial class MyControl : UserControl
{
public MyItem MyValue
{
get { return (MyItem)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("MyValue",
typeof(MyItem), typeof(MyControl),
new PropertyMetadata(
new PropertyChangedCallback(PropertyChanged)));
public MyControl()
{
InitializeComponent();
}
private static void PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyControl c = obj as MyControl;
if (c != null)
{
// TODO: do something here
}
}
}
MyControl.DataContext property would already be bound to your item view model in this case.
Your MyItem is the data context - if you want to bind to some member of My Item then its:
Binding Path=my_item_member
To bind to MyItem in its entirety I think you want:
Binding Path=.
Your might also need a DataTemplate for your control (in addition to the you have for the ListBoxItems) that maps the MyItems members to your control fields.

Categories