Here is my ViewModel class:
public class ColumnViewModel : ViewModelBase
{
private string bindingPropName;
public string BindingPropName
{
get { return bindingPropName; }
set
{
if (bindingPropName != value)
{
bindingPropName = value;
RaisePropertyChanged("BindingPropName");
}
}
}
private string header;
public string Header
{
get { return header; }
set
{
if (header != value)
{
header = value;
RaisePropertyChanged("Header");
}
}
}
}
DataGrid extension classes:
public static class DataGridColumns
{
static DataGridColumns()
{
FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridTextColumn));
}
private static readonly DependencyProperty DataGridColumnSettingsProperty = DependencyProperty.RegisterAttached(
"DataGridColumnSettings",
typeof(DataGridColumnSettings),
typeof(DataGridColumn));
private static void SetDataGridColumnSettings(DataGridColumn column, DataGridColumnSettings settings) { column.SetValue(DataGridColumnSettingsProperty, settings); }
private static DataGridColumnSettings GetDataGridColumnSettings(DataGridColumn column) { return column.GetValue(DataGridColumnSettingsProperty) as DataGridColumnSettings; }
public static readonly DependencyProperty DisplayColumnsProperty = DependencyProperty.RegisterAttached(
"DisplayColumns",
typeof(IList),
typeof(DataGridColumns),
new PropertyMetadata(null, DisplayColumnsPropertyChanged));
public static void SetDisplayColumns(DataGrid dataGrid, IList columns) { dataGrid.SetValue(DisplayColumnsProperty, columns); }
public static IList GetDisplayColumns(DataGrid dataGrid) { return dataGrid.GetValue(DisplayColumnsProperty) as IList; }
private static void DisplayColumnsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as DataGrid;
var columns = e.NewValue as IList;
var template = GetColumnSettingsTemplate(target);
CreateColumns(target, columns, template);
}
public static readonly DependencyProperty ColumnSettingsTemplateProperty = DependencyProperty.RegisterAttached(
"ColumnSetupTemplate",
typeof(DataTemplate),
typeof(DataGridColumns),
new PropertyMetadata(null, ColumnSettingsTemplateChanged));
public static void SetColumnSettingsTemplate(DataGrid dataGrid, DataTemplate columnSetupTemplate) { dataGrid.SetValue(ColumnSettingsTemplateProperty, columnSetupTemplate); }
public static DataTemplate GetColumnSettingsTemplate(DataGrid dataGrid) { return dataGrid.GetValue(ColumnSettingsTemplateProperty) as DataTemplate; }
private static void ColumnSettingsTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as DataGrid;
var columns = GetDisplayColumns(target);
var template = e.NewValue as DataTemplate;
CreateColumns(target, columns, template);
}
private static void CreateColumns(DataGrid dataGrid, IList columnViewModels, DataTemplate columnSettings)
{
if (dataGrid == null)
return;
dataGrid.Columns.Clear();
if (columnViewModels == null)
return;
foreach (var column in columnViewModels)
{
var newColumn = new DataGridTextColumn();
newColumn.SetValue(FrameworkElement.DataContextProperty, column);
if (columnSettings != null)
{
var settings = columnSettings.LoadContent() as DataGridColumnSettings;
if (settings != null)
{
settings.Setup(newColumn, column);
SetDataGridColumnSettings(newColumn, settings);
}
}
dataGrid.Columns.Add(newColumn);
}
}
}
public class DataGridColumnSettings : FrameworkElement
{
public static readonly DependencyProperty ColumnBindingPathProperty = DependencyProperty.Register(
"ColumnBindingPath",
typeof(string),
typeof(DataGridColumnSettings),
new PropertyMetadata(null, ColumnBindingPathChanged));
public string ColumnBindingPath
{
get { return GetValue(ColumnBindingPathProperty) as string; }
set { SetValue(ColumnBindingPathProperty, value); }
}
private static void ColumnBindingPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as DataGridColumnSettings;
if (target == null)
return;
target.column.Binding = new Binding(e.NewValue as string);
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(object),
typeof(DataGridColumnSettings));
public object Header
{
get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
private DataGridTextColumn column;
private object viewModel;
public void Setup(DataGridTextColumn column, object columnViewModel)
{
this.column = column;
viewModel = columnViewModel;
this.DataContext = columnViewModel;
if (Header is FrameworkElement)
{
(Header as FrameworkElement).DataContext = columnViewModel;
column.Header = Header;
}
else
BindingOperations.SetBinding(column, DataGridColumn.HeaderProperty, new Binding("Header") { Source = this });
column.Binding = new Binding(ColumnBindingPath);
}
}
and my XAML code:
<DataGrid t:DataGridColumns.DisplayColumns="{Binding Columns}" ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<t:DataGridColumns.ColumnSettingsTemplate>
<DataTemplate>
<t:DataGridColumnSettings ColumnBindingPath="{Binding BindingPropName}">
<t:DataGridColumnSettings.Header>
<TextBlock Text="{Binding Header}"/>
</t:DataGridColumnSettings.Header>
</t:DataGridColumnSettings>
</DataTemplate>
</t:DataGridColumns.ColumnSettingsTemplate>
</DataGrid>
Everything what i'm trying to achieve is adding CellTemplate:
<DataGrid t:DataGridColumns.DisplayColumns="{Binding Columns}" ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<t:DataGridColumns.ColumnSettingsTemplate>
<DataTemplate>
<t:DataGridColumnSettings ColumnBindingPath="{Binding BindingPropName}">
<t:DataGridColumnSettings.Header>
<TextBlock Text="{Binding Header}"/>
</t:DataGridColumnSettings.Header>
<t:DataGridColumnSettings.CellTemplate>
<TextBlock Text="{Binding ColumnBindingPath}"/>
</t:DataGridColumnSettings.CellTemplate>
</t:DataGridColumnSettings>
</DataTemplate>
</t:DataGridColumns.ColumnSettingsTemplate>
</DataGrid>
I think the easiest approach would be to add one more dependency property to take a cell template. Then if this exist, create DataGridTemplateColumn instead of DataGridTextColumn when filling the DataGrid, but i have a little problem with Binding my CellTemplate TextBlock to ColumnBindingPath dependency property. Please help ...
The syntax for binding to an attached property would be:
<TextBlock Text="{Binding Path=(t.DataGridColumns.ColumnBindingPath)}"/>
The Path= needs to be written explicitly when binding to attached properties. This has been fixed in WPF 4.5, so there it may be sufficient to write
<TextBlock Text="{Binding (t.DataGridColumns.ColumnBindingPath)}"/>
Note, that you still need parenthesis around the property. Does this answer your question already?
Related
I am working on an executive dashboard that should be able to have any number of charts each with any number of series. I am using the WPF Toolkit.
The first problem I had was binding multiple series to a chart. I found Beat Kiener's excellent blogpost on binding multiple series to a chart which worked great until I put it in an items control, which I have to do to meet my "any number of charts" requirement.
It seems to me that the below code should work, but it does not. Can anyone explain why the below code does not work, or offer another way to do it using MVVM?
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ChartData ChartData { get; set; }
public List<ChartData> ChartDataList { get; set; }
public MainWindow()
{
var dataSeries = new Dictionary<string, int>();
dataSeries.Add("Jan", 5);
dataSeries.Add("Feb", 7);
dataSeries.Add("Mar", 3);
ChartData = new ChartData();
ChartData.Title = "Chart Title";
ChartData.DataSeriesList = new List<Dictionary<string, int>>();
ChartData.DataSeriesList.Add(dataSeries);
ChartDataList = new List<ChartData>();
ChartDataList.Add(ChartData);
InitializeComponent();
this.DataContext = this;
}
}
public class ChartData
{
public string Title { get; set; }
public List<Dictionary<string, int>> DataSeriesList { get; set; }
}
MainWindow.xaml
<UniformGrid
Rows="1">
<!-- These charts do not work -->
<ItemsControl
x:Name="itemsControl"
ItemsSource="{Binding ChartDataList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MultiChart
Title="{Binding Title}"
SeriesSource="{Binding DataSeriesList}">
<local:MultiChart.SeriesTemplate>
<DataTemplate >
<chartingToolkit:ColumnSeries
Title="Series Title"
ItemsSource="{Binding}"
IndependentValueBinding="{Binding Key}"
DependentValueBinding="{Binding Value}"/>
</DataTemplate>
</local:MultiChart.SeriesTemplate>
</local:MultiChart>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- End of not working charts -->
<!-- This chart works -->
<local:MultiChart
Title="{Binding ChartData.Title}"
SeriesSource="{Binding ChartData.DataSeriesList}">
<local:MultiChart.SeriesTemplate>
<DataTemplate>
<chartingToolkit:ColumnSeries
Title="Series Title"
ItemsSource="{Binding}"
IndependentValueBinding="{Binding Key}"
DependentValueBinding="{Binding Value}" />
</DataTemplate>
</local:MultiChart.SeriesTemplate>
</local:MultiChart>
<!-- End of working chart -->
</UniformGrid>
MultiChart.cs
public class MultiChart : System.Windows.Controls.DataVisualization.Charting.Chart
{
#region SeriesSource (DependencyProperty)
public IEnumerable SeriesSource
{
get
{
return (IEnumerable)GetValue(SeriesSourceProperty);
}
set
{
SetValue(SeriesSourceProperty, value);
}
}
public static readonly DependencyProperty SeriesSourceProperty = DependencyProperty.Register(
name: "SeriesSource",
propertyType: typeof(IEnumerable),
ownerType: typeof(MultiChart),
typeMetadata: new PropertyMetadata(
defaultValue: default(IEnumerable),
propertyChangedCallback: new PropertyChangedCallback(OnSeriesSourceChanged)
)
);
private static void OnSeriesSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IEnumerable oldValue = (IEnumerable)e.OldValue;
IEnumerable newValue = (IEnumerable)e.NewValue;
MultiChart source = (MultiChart)d;
source.OnSeriesSourceChanged(oldValue, newValue);
}
protected virtual void OnSeriesSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
this.Series.Clear();
if (newValue != null)
{
foreach (object item in newValue)
{
DataTemplate dataTemplate = null;
if (this.SeriesTemplate != null)
{
dataTemplate = this.SeriesTemplate;
}
// load data template content
if (dataTemplate != null)
{
Series series = dataTemplate.LoadContent() as Series;
if (series != null)
{
// set data context
series.DataContext = item;
this.Series.Add(series);
}
}
}
}
}
#endregion
#region SeriesTemplate (DependencyProperty)
public DataTemplate SeriesTemplate
{
get
{
return (DataTemplate)GetValue(SeriesTemplateProperty);
}
set
{
SetValue(SeriesTemplateProperty, value);
}
}
public static readonly DependencyProperty SeriesTemplateProperty = DependencyProperty.Register(
name: "SeriesTemplate",
propertyType: typeof(DataTemplate),
ownerType: typeof(MultiChart),
typeMetadata: new PropertyMetadata(default(DataTemplate))
);
#endregion
}
I finally figured it out.
When you put the MultiChart inside of an ItemsControl the SeriesSource property is set BEFORE the SeriesTemplate property. This does not work because the OnSeriesSourceChanged method needs to know what the SeriesTemplate is. My workaround is to just call the OnSeriesSourceChanged method whenever the SeriesTemplate is changed.
private static void OnSeriesTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataTemplate oldValue = (DataTemplate)e.OldValue;
DataTemplate newValue = (DataTemplate)e.NewValue;
MultiChart source = (MultiChart)d;
source.OnSeriesTemplateChanged(oldValue, newValue);
}
protected virtual void OnSeriesTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
{
this.SeriesTemplate = newValue;
OnSeriesSourceChanged(SeriesSource, SeriesSource);
}
public static readonly DependencyProperty SeriesTemplateProperty = DependencyProperty.Register(
name: "SeriesTemplate",
propertyType: typeof(DataTemplate),
ownerType: typeof(MultiChart),
typeMetadata: new PropertyMetadata(
defaultValue: default(DataTemplate),
propertyChangedCallback: new PropertyChangedCallback(OnSeriesTemplateChanged)
)
);
i need an ItemsControl that displays only the selected Item so i have written a customized ItemsControl as seen here:
public class TabView : ItemsControl
{
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(object),
typeof(TabView),
new PropertyMetadata(new PropertyChangedCallback(SelectedItemPropertyChanged)));
private static void SelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { OnSelectedItemChanged(value); }
}
public TabView()
: base()
{
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (!IsItem(SelectedItem))
{
if (Items.Count > 0)
SelectedItem = Items[0];
else
SelectedItem = null;
}
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override DependencyObject GetContainerForItemOverride()
{
Grid grid = new Grid();
grid.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
grid.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
return grid;
}
protected virtual void OnSelectedItemChanged(object newItem)
{
if (SelectedItem == newItem) return;
if (!IsItem(newItem))
{
Debugger.Log(0, "TRACE", "TABVIEW: Index Out of Bounds"+Environment.NewLine);
return;
}
foreach (var item in Items)
{
var container = ItemContainerGenerator.ContainerFromItem(item);
if (container != null)
{
if (item == newItem)
container.SetValue(Control.VisibilityProperty, Visibility.Visible);
else
container.SetValue(Control.VisibilityProperty, Visibility.Collapsed);
}
}
SetValue(SelectedItemProperty, newItem);
}
private bool IsItem(object item)
{
return Items.Contains(item);
}
}
Now I bind my Dataobject SelectedDataTab (MainViewModel.SelectedDataTab) to TabView.SelectedItem.
<uc:TabView
ItemsSource="{Binding BrowserTabs}"
SelectedItem="{Binding SelectedDataTab}">
<uc:TabView.ItemTemplate>
<DataTemplate>
<local:SlitteTabViewSelector Content="{Binding}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<local:SlitteTabViewSelector.Browser>
<DataTemplate>
<uc:CustomBrowser Margin="0,0,0,0" />
</DataTemplate>
</local:SlitteTabViewSelector.Browser>
<local:SlitteTabViewSelector.MoreTabs>
<DataTemplate>
<uc:MoreTabsControl Margin="0,0,0,0" />
</DataTemplate>
</local:SlitteTabViewSelector.MoreTabs>
</local:SlitteTabViewSelector>
</DataTemplate>
</uc:TabView.ItemTemplate>
</uc:TabView>
If I now start changing SelectedDataTab in my MainViewModel TabView.SelectedItem won't change its value! Did I miss something? I thought if I bind that Property to my MainViewModel.SelectedDataTab it's value gets updated depending on the bound value.
Update: My MainViewModel implements INotifyPropertyChanged correctly and SelectedDataTab will fire it if changed.
RESOLVED
First of all not the Property getter/setter is fired by Binding instead SelectedItemPropertyChanged gets fired.
But in my code SelectedItemPropertyChanged will not get fired because
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (!IsItem(SelectedItem))
{
if (Items.Count > 0)
SelectedItem = Items[0];
else
SelectedItem = null;
}
}
overrides the DataBinding. ;)
I try to use this xaml, to apply an event to command binding:
<telerik:RadGridView x:Name="xRadGridView"
prismcommands:SelectionChangedCommand.Command="{Binding SelectPersonCommand}"
ItemsSource="{Binding GridItems, Mode=TwoWay}">
</telerik:RadGridView>
I get the error:
'SelectionChangedCommand.Command' property is read-only and cannot be
set from markup.
I can bind to prismcommands:RowEditEndedCommand.Command with no problem.
Is there any chance to bind to SelectionChangedCommand.Command?
I use the same PrismCommands in a Silverlight project and it works there.
namespace RadEventToCommand.WPF.PrismCommands
{
public class RowEditEndedCommandBehavior : CommandBehaviorBase<RadGridView>
{
public RowEditEndedCommandBehavior(RadGridView gridView)
: base(gridView)
{
gridView.RowEditEnded +=new EventHandler<GridViewRowEditEndedEventArgs>(gridView_RowEditEnded);
}
void gridView_RowEditEnded(object sender, GridViewRowEditEndedEventArgs e)
{
CommandParameter = e;
ExecuteCommand();
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public static class SelectionChangedCommand
{
private static readonly DependencyProperty SelectionChangedCommandBehaviorProperty
= DependencyProperty.RegisterAttached(
"SelectionChangedCommandBehavior",
typeof(SelectionChangedCommandBehavior),
typeof(SelectionChangedCommand),
null);
public static readonly DependencyProperty CommandProperty
= DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(SelectionChangedCommand),
new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty CommandParameterProperty
= DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(SelectionChangedCommand),
new PropertyMetadata(OnSetCommandParameterCallback));
public static ICommand GetCommand(RadGridView gridView)
{
return gridView.GetValue(CommandProperty) as ICommand;
}
public static void SetCommandParameter(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandParameterProperty, parameter);
}
public static object GetCommandParameter(RadGridView gridView)
{
return gridView.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
SelectionChangedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
SelectionChangedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.CommandParameter = e.NewValue;
}
}
private static SelectionChangedCommandBehavior GetOrCreateBehavior(RadGridView gridView)
{
SelectionChangedCommandBehavior behavior =
gridView.GetValue(SelectionChangedCommandBehaviorProperty) as SelectionChangedCommandBehavior;
if (behavior == null)
{
behavior = new SelectionChangedCommandBehavior(gridView);
gridView.SetValue(SelectionChangedCommandBehaviorProperty, behavior);
}
return behavior;
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public class RowEditEndedCommandBehavior : CommandBehaviorBase<RadGridView>
{
public RowEditEndedCommandBehavior(RadGridView gridView)
: base(gridView)
{
gridView.RowEditEnded +=new EventHandler<GridViewRowEditEndedEventArgs>(gridView_RowEditEnded);
}
void gridView_RowEditEnded(object sender, GridViewRowEditEndedEventArgs e)
{
CommandParameter = e;
ExecuteCommand();
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public static class RowEditEndedCommand
{
private static DependencyProperty RowEditEndedCommandBehaviorProperty
= DependencyProperty.RegisterAttached(
"RowEditEndedCommandBehavior",
typeof(RowEditEndedCommandBehavior),
typeof(RowEditEndedCommand),
null);
public static DependencyProperty CommandProperty
= DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(RowEditEndedCommand),
new PropertyMetadata(OnSetCommandCallback));
public static DependencyProperty CommandParameterProperty
= DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(RowEditEndedCommand),
new PropertyMetadata(OnSetCommandParameterCallback));
public static ICommand GetCommand(RadGridView gridView)
{
return gridView.GetValue(CommandProperty) as ICommand;
}
public static void SetCommand(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandProperty, parameter);
}
public static void SetCommandParameter(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandParameterProperty, parameter);
}
public static object GetCommandParameter(RadGridView gridView)
{
return gridView.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
RowEditEndedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
RowEditEndedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.CommandParameter = e.NewValue;
}
}
private static RowEditEndedCommandBehavior GetOrCreateBehavior(RadGridView gridView)
{
RowEditEndedCommandBehavior behavior =
gridView.GetValue(RowEditEndedCommandBehaviorProperty) as RowEditEndedCommandBehavior;
if (behavior == null)
{
behavior = new RowEditEndedCommandBehavior(gridView);
gridView.SetValue(RowEditEndedCommandBehaviorProperty, behavior);
}
return behavior;
}
}
}
I had the source for the behavior copied over from a Silverlight project. It worked there. For some reason in WPF I need the additional method in SelectionChangedCommand
public static void SetCommand(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandProperty, parameter);
}
I copied the code over to check if I could use a common codebase for Silverlight and WPF.
For the RadGridView, we are using the Interaction Triggers. The below code works for us.
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Does anyone know how to make a custom ItemsSource?
What I want to do is to make an itemsSource to my own UserControl so that it could be bound by ObservableCollection<>.
Also, I could know Whenever the number of items in the itemsSource updated, so as to do further procedures.
Thank you so much.
You may need to do something like this in your control
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
// Remove handler for oldValue.CollectionChanged
var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;
if (null != oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
// Add handler for newValue.CollectionChanged (if possible)
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Do your stuff here.
}
Use a DependencyProperty ItemsSource in your CustomControl and then bind to this DependencyProperty
This is the XAML-Code (Recognize the DataContext of the ListBox):
<UserControl
x:Name="MyControl">
<ListBox
DataContext="{Binding ElementName=MyControl}"
ItemsSource="{Binding ItemsSource}">
</ListBox>
</UserControl>
This is the CodeBehind:
public partial class MyCustomControl
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable),
typeof(ToolboxElementView), new PropertyMetadata(null));
}
This is the Code, where you use your "MyCustomControl":
<Window>
<local:MyCustomControl
ItemsSource="{Binding MyItemsIWantToBind}">
</local:MyCustomControl>
</Window>
Simplified answer.
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(null, (s, e) =>
{
if (s is UserControl1 uc)
{
if (e.OldValue is INotifyCollectionChanged oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= uc.ItemsSource_CollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += uc.ItemsSource_CollectionChanged;
}
}
}));
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Logic Here
}
// Do Not Forget To Remove Event On UserControl Unloaded
private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
if (ItemsSource is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= ItemsSource_CollectionChanged;
}
}
I use this:
<TextBox x:Name="Test"/>
<TextBlock Text="{Binding SelectionStart, ElementName=Test}"/>
but it always shows 0.
How can I treat it?
Thank you.
I ran into this problem (SelectionStart and SelectionLength are not dependency properties) and decided to make a TextBox with bindable selection start and end:
public class SelectionBindingTextBox : TextBox
{
public static readonly DependencyProperty BindableSelectionStartProperty =
DependencyProperty.Register(
"BindableSelectionStart",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionStartChanged));
public static readonly DependencyProperty BindableSelectionLengthProperty =
DependencyProperty.Register(
"BindableSelectionLength",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionLengthChanged));
private bool changeFromUI;
public SelectionBindingTextBox() : base()
{
this.SelectionChanged += this.OnSelectionChanged;
}
public int BindableSelectionStart
{
get
{
return (int)this.GetValue(BindableSelectionStartProperty);
}
set
{
this.SetValue(BindableSelectionStartProperty, value);
}
}
public int BindableSelectionLength
{
get
{
return (int)this.GetValue(BindableSelectionLengthProperty);
}
set
{
this.SetValue(BindableSelectionLengthProperty, value);
}
}
private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (!textBox.changeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionStart = newValue;
}
else
{
textBox.changeFromUI = false;
}
}
private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (!textBox.changeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionLength = newValue;
}
else
{
textBox.changeFromUI = false;
}
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
if (this.BindableSelectionStart != this.SelectionStart)
{
this.changeFromUI = true;
this.BindableSelectionStart = this.SelectionStart;
}
if (this.BindableSelectionLength != this.SelectionLength)
{
this.changeFromUI = true;
this.BindableSelectionLength = this.SelectionLength;
}
}
}
You cannot bind to SelectionStart because it is not a DependencyProperty.
This could be an alternate solution:
View:
<TextBox Text="{Binding Text}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvml:EventToCommand Command="{Binding TextBoxSelectionChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
ViewModel:
#region TextBoxSelectionChangedCommand
RelayCommand<RoutedEventArgs> _TextBoxSelectionChangedCommand = null;
public ICommand TextBoxSelectionChangedCommand {
get {
if (_TextBoxSelectionChangedCommand == null) {
_TextBoxSelectionChangedCommand = new RelayCommand<RoutedEventArgs>((r) => TextBoxSelectionChanged(r), (r) => true);
}
return _TextBoxSelectionChangedCommand;
}
}
protected virtual void TextBoxSelectionChanged(RoutedEventArgs _args) {
YourCursorPositionVariable = (_args.OriginalSource as System.Windows.Controls.TextBox).SelectionStart;
}
#endregion
I agree you has to cast TextBox component type in ViewModel and it's a kind of coupling, but create a custom component will impose to bind on a specific property as well.
As far as I am aware, this feature has not been included in Silverlight 2.0.
Read this article for a work-around solution.