I have a WPF application based on MVVM.
It has paramaters that can be set. A paramater can be a list of strings in a checkbox or a double in a numupdown. Every device has its own parameters, so the parameters are dynamically loaded. Thats why i use a DataTemplateSelector.
Xaml code
<ItemsControl ItemsSource="{Binding ParameterWrapperList}" ItemTemplateSelector="{StaticResource propertyDataTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The templates are in a resource dictionary:
<DataTemplate x:Key="doubleTemplate">
<Grid Visibility="{Binding Parameter.ParameterModel.IsVisible, Converter={StaticResource Visibility}}">
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=RowHeight, Mode=OneWay}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{StaticResource HeaderBorderStyle}" Width="{Binding Width}" Visibility="{Binding HeaderVisibility, Converter={StaticResource Visibility}}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Parameter.DisplayName}"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Style="{StaticResource ItemBorderStyle}" Width="{Binding Width}">
<telerik:RadNumericUpDown Name="nudParameterValue" Value="{Binding Path=ParameterValue, Mode=TwoWay}" Minimum="{Binding Parameter.ParameterModel.MinimumValue}" Maximum="{Binding Parameter.ParameterModel.MaximumValue}" NumberDecimalDigits="{Binding Parameter.ParameterModel.NumberDecimalDigits}" SmallChange="{Binding Parameter.ParameterModel.SmallChanges}"/>
</Border>
</Grid>
</DataTemplate>
<DataTemplate x:Key="stringTemplate">
<Grid Visibility="{Binding Parameter.ParameterModel.IsVisible, Converter={StaticResource Visibility}}">
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=RowHeight, Mode=OneWay}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{StaticResource HeaderBorderStyle}" Width="{Binding Width}" Visibility="{Binding Path=HeaderVisibility, Converter={StaticResource Visibility}}">
<Label Content="{Binding Parameter.DisplayName}"/>
</Border>
<Border Grid.Row="1" Style="{StaticResource ItemBorderStyle}" Width="{Binding Width}" >
<ComboBox ItemsSource="{Binding Path=Parameter.ParameterModel.PossibleValues}" SelectedIndex="{Binding ParameterValue, Mode=TwoWay}">
</ComboBox>
</Border>
</Grid>
</DataTemplate>
<configControl:PropertyDataTemplateSelector
StringTemplate="{StaticResource stringTemplate}"
DoubleTemplate="{StaticResource doubleTemplate}"
x:Key="propertyDataTemplateSelector"/>
I wrote the code for the DataTemplateSelector.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DoubleTemplate { get; set; }
public DataTemplate StringTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DeviceModel.ParameterType devFunct = ((ParameterWrapper)item).Parameter.ParameterModel.Type;
switch (devFunct)
{
case DeviceModel.ParameterType.Double:
return DoubleTemplate;
case DeviceModel.ParameterType.String:
return StringTemplate;
default:
return null;
}
return null;
}
}
When the applications opens all the values are set fine. But when i update ParameterValue, it isn't updated on view. (when i output the values in console window, the values are set fine (but not in the GUI)).
What should i do to make this work.
Edit
This is the code it's bound to. Every function has a checkbox and when you change the parameter of one, all other parameters that are checked need to change.
public double ParameterValue
{
get
{
return _parameter.ParameterValue;
}
set
{
Parameter.ParameterValue = value;
Console.WriteLine("ParameterValueChanged");
if (SetOthers)
{
if (_parentFunction.Checked)
{
foreach (var function in _parentFunction.FunctionWrapperList)
{
if (function.Checked)
{
foreach (var parameterWrapper in function.ParameterWrapperList)
{
if (parameterWrapper.Parameter != this.Parameter)
{
if (parameterWrapper.Parameter.ParameterModel == Parameter.ParameterModel)
{
Console.WriteLine(function.Function.Name + " " + function.Checked + " " + Parameter.ParameterValue);
parameterWrapper.SetValue(Parameter.ParameterValue);
}
}
}
}
}
}
}
Notify("ParameterValue");
}
}
private void SetValue(double parameterValue)
{
SetOthers = false;
Parameter.ParameterValue = parameterValue;
SetOthers = true;
}
The boolean SetOthers is so he won't get a stackoverflow and keeps setting itself.
Your DataTemplateSelector won't update the DataTemplate rendering your data because a DataTemplateSelector doesn't trigger on a property change. It only choose a way to display a data according to its type (yourData.GetType()).
What you want is a trigger:
<ItemsControl ItemsSource="{Binding ParameterWrapperList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource doubleTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Parameter.ParameterModel.Type}" Value="{x:Static local:DeviceModel.ParameterType.String}">
<Setter Property="ContentTemplate" Value="{StaticResource stringTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Related
I have a model view and a property called isEnable and I would like to set the comboboxItem IsEnable property with binding to property created in the model view , the problem is that I have datatemplate created inside a combobox , so it's not a simple Combobox , this is my code:
<ComboBox x:Name="ComboBoxUsers" ItemsSource="{Binding Users}" FontFamily="Arial" FontSize="11" Grid.Row="3" Height="30" Margin="10,5,5,5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image />
<TextBlock />
</StackPanel>
<DataTemplate.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnable" Value="{Binding IsEnable}"/>
</Style>
</DataTemplate.Resources>
</DataTemplate>
</ComboBox.ItemTemplate>
Simple Class of Users:
public class Users
{
public string Name { get; set; }
public bool IsEnable { get; set; }
public Users()
{
}
}
How can I achieve that?
I solved my problem by adding this code :
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem"
<Setter Property="IsEnable" Value="{Binding IsEnable}" />
</Style>
</ComboBox.ItemContainerStyle>
So the complete code will be :
<ComboBox x:Name="ComboBoxUsers" ItemsSource="{Binding Users}" FontFamily="Arial" FontSize="11" Grid.Row="3" Height="30" Margin="10,5,5,5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image />
<TextBlock />
</StackPanel>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem" >
<Setter Property="IsEnable" Value="{Binding IsEnable}" />
</Style>
</ComboBox.ItemContainerStyle>
</DataTemplate>
</ComboBox.ItemTemplate>
In my WPF Application, I have Item control which shows Dates and Days of a week.
Now,I need to change the background color of the Item which is today's date.
Here is the XAML code
<ItemsControl Grid.Column="1"
Focusable="False"
ItemsSource="{Binding WeekDays}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate >
<DataTemplate>
<Border BorderBrush="{StaticResource GrayBrush7}"
BorderThickness="1,0,0,0"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<StackPanel Margin="2"
VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Top"
Text="{Binding Day}"
TextAlignment="Center"/>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Top"
Text="{Binding Date,
StringFormat='dd/MM/yyyy'}"
TextAlignment="Center"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks in Advance.
You can use DataTrigger in Border Style if you have IsToday property in model class. Like this
public class WeekDay
{
public DateTime Date { get; }
public string Day { get; }
public bool IsToday { get; }
public WeekDay(DateTime date)
{
this.Date = date;
this.Day = date.DayOfWeek.ToString();
this.IsToday = date.Date == DateTime.Today;
}
}
<ItemsControl.ItemTemplate >
<DataTemplate>
<Border BorderBrush="{StaticResource GrayBrush7}"
BorderThickness="1,0,0,0"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsToday}" Value="True">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
...
As an alternative, you can use ValueConverter to convert Date to Background.
public class DateToItemBackgroundConverter : IValueConverter
{
private static readonly Brush TodayBGBrush = new SolidColorBrush(Colors.Yellow);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || !(value is DateTime))
{
return value;
}
var isToday = ((DateTime)value).Date == DateTime.Today;
return isToday ? TodayBGBrush : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<ItemsControl Grid.Column="1"
Focusable="False"
ItemsSource="{Binding WeekDays}">
<ItemsControl.Resources>
<local:DateToItemBackgroundConverter x:Key="bgConverter" />
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate >
<DataTemplate>
<Border BorderBrush="{StaticResource GrayBrush7}"
Background="{Binding Date, Converter={StaticResource bgConverter}}"
BorderThickness="1,0,0,0"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
...
Remember if you wanna refresh background when TODAY is updated, you should rebuild model data or notify PropertyChanged again.
You could just add a Border style with a DataTrigger to your ItemTemplate. Compare the Date property of your DateTime property with the static DateTime.Today property:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{StaticResource GrayBrush7}"
BorderThickness="1,0,0,0"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Border.Style>
<Style TargetType="Border" xmlns:system="clr-namespace:System;assembly=mscorlib">
<Style.Triggers>
<DataTrigger Binding="{Binding Date.Date}" Value="{x:Static system:DateTime.Today}">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Margin="2"
VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Top"
Text="{Binding Day}"
TextAlignment="Center"/>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Top"
Text="{Binding Date, StringFormat='dd/MM/yyyy'}"
TextAlignment="Center"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
I would like to draw in canvas different shapes. How can I make object from ArrowsItems ObservableCollection and CircleItems ObservableCollecton visible in canvas? I am also creating Shapes ObservableCollection including every Circle and Arrows Items. I think that propably the reason is in the data binding but don't know where.
The goal is possibility to generate and then draw programmatically circles and arrows.
<Button Grid.Row="1" MaxWidth="1000" Command="{Binding CreateEllipse}">Utwórz</Button>
<Viewbox Grid.Row="2" Margin="0 20 0 0" Stretch="Uniform" StretchDirection="Both" VerticalAlignment="Stretch">
<ItemsControl Name="Shape" ItemsSource="{Binding Shapes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="2000" Height="1200" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X, Mode=TwoWay}"/><Setter Property="Canvas.Top" Value="{Binding Y, Mode=TwoWay}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type core:CircleItem}">
<Viewbox Width="{Binding Width}" Height="{Binding Height}">
<!--MouseMove="Viewbox_MouseMove"-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<i:InvokeCommandAction Command="{Binding DataContext.MyCommand, ElementName=Shape}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<i:Interaction.Behaviors>
<local:DragBehavior/>
</i:Interaction.Behaviors>
<Grid>
<Grid.RenderTransform>
<TranslateTransform X="{Binding TransformX}" Y="{Binding TransformY}" />
</Grid.RenderTransform>
<Ellipse Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding Color}" />
<TextBlock HorizontalAlignment="Center" Text="{Binding Text}" TextAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Viewbox>
</DataTemplate>
<DataTemplate DataType="{x:Type core:ArrowItem}">
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" Stroke="{Binding Color}" StrokeThickness="{Binding StrokeThickness}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Viewbox>
Also in my ViewModel:
public ObservableCollection<CircleItem> CircleItems { get; set; }
public ObservableCollection<ArrowItem> ArrowItems { get; set; }
public CompositeCollection Shapes { get; set; }
And after adding some objects of CircleItem class to CircleItems and ArrowItem to ArrowItems:
CompositeCollection coll = new CompositeCollection();
coll.Add(new CollectionContainer() { Collection = CircleItems });
coll.Add(new CollectionContainer() { Collection = ArrowItems });
Shapes = coll;
Make sure you initialize the Shapes property before the view model is assigned to the DataContext. The collection properties should all be readonly, otherwise you would have to fire a property change notification from their setters.
public class ViewModel
{
public ObservableCollection<CircleItem> CircleItems { get; }
= new ObservableCollection<CircleItem>();
public ObservableCollection<ArrowItem> ArrowItems { get; }
= new ObservableCollection<ArrowItem>();
public CompositeCollection Shapes { get; }
= new CompositeCollection();
public ViewModel()
{
Shapes.Add(new CollectionContainer { Collection = CircleItems });
Shapes.Add(new CollectionContainer { Collection = ArrowItems });
}
}
If I understood you correctly, you want to have different templates for different datatypes. This is quite easy to get. One of the ways (probably the simplest one):
create data templates for every of your types you want to show
create some kind of data template that will select the proper template:
<!-- Data template for arrows -->
<DataTemplate x:Key="ArrowsDataTemplate" DataType="{x:Type core:ArrowItem}">
<!-- Create here your template for arrows -->
</DataTemplate>
<!-- create data templates for other stuff and then "selector" data template -->
<DataTemplate x:Key="ContentDataTemplate">
<ContentPresenter x:Name="itemContentPresenter"
ContentTemplate="{StaticResource CircleDataTemplate}" <!-- just the default one -->
Content="{TemplateBinding Content}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding MyItemTypeAsEnum}" Value="Arrow">
<Setter TargetName="itemContentPresenter" Property="ContentTemplate" Value="{StaticResource ArrowsDataTemplate"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<!-- Now in your control (for example content control), where you show this stuff, do: -->
<ContentControl ContentTemplate="{StaticResource ContentDataTemplate}"/>
Now, I assumed that you have one base Item with property MyItemTypeAsEnum which will give you Circle for CircleItem, Arrow for ArrowItem etc. But if you don't have such property, you should be able to get boolean value from your viewmodel that will tell you if this item is Circle or not etc.
Into your main control, when you show thigs you have to set ContentTemplate to your "selector" template.
I want seperate ScrollBar for each columns
<ListView ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding name}"/>
<GridViewColumn Header="Price"
DisplayMemberBinding="{Binding price}"/>
</GridView>
</ListView.View>
</ListView>
and i want to have 2 horizontal scrollbar for each column (Name and Price),
my destination is to set up column with picture, but i want to scroll that picture when its to big.
I found a way to add scrolls only in the bottom of a gridview based on this solution: How can I create a group footer in a WPF ListView ( GridView ). It looks kinda hacky but it seems there is no already made solution.
What I did in xaml is bind Group description to a fake property for show the footer only one time.
XAML:
<PropertyGroupDescription PropertyName="fake" />
Then I modified a footer template to make it display a scrollbar:
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<DockPanel>
<Grid DockPanel.Dock="Bottom" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
<ColumnDefinition Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ColumnDefinitions>
<ScrollBar Grid.Column="0" Orientation="Horizontal" Scroll="ScrollBar1_Scroll"
Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" >
</ScrollBar>
<ScrollBar Grid.Column="1" Orientation="Horizontal" Scroll="ScrollBar2_Scroll"
Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
</Grid>
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There's a lot of bindings which are necessary for resizing scrollbars with columns. Another hack there: you have to initially set the Width for each GridViewColumn or scrollbars will appear only after you manually resize a column.
I move contents of cells changing Margin property of StackPanel in the CellTemplate.
Below is the whole code, it's not a full working solution, I think, but a good point to start.
<Window x:Class="WpfApplication1.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" Loaded="Window_Loaded">
<Grid>
<Grid.Resources>
<CollectionViewSource x:Key="ViewSource" Source="{Binding Path=Collection, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window} }">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="fake" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Grid.Resources>
<HeaderedItemsControl Header="ListView">
<StackPanel>
<ListView ItemsSource="{Binding Source={StaticResource ViewSource}, UpdateSourceTrigger=PropertyChanged}" x:Name="Lv" Margin="5,5,5,5">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<DockPanel>
<Grid DockPanel.Dock="Bottom" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
<ColumnDefinition Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ColumnDefinitions>
<ScrollBar Grid.Column="0" Orientation="Horizontal" Scroll="ScrollBar1_Scroll"
Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" >
</ScrollBar>
<ScrollBar Grid.Column="1" Orientation="Horizontal" Scroll="ScrollBar2_Scroll"
Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
</Grid>
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.View >
<GridView >
<GridViewColumn Header="Name" x:Name="first" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="{Binding Path=Offset1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window} }">
<TextBlock x:Name="tb1" Text="{Binding name}"></TextBlock>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Price" x:Name="second" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="{Binding Path=Offset2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window} }" >
<TextBlock Text="{Binding price}"></TextBlock>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</HeaderedItemsControl>
</Grid>
</Window>
And code-behind
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace WpfApplication1
{
public partial class MainWindow:INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
private List<MyItem> _collection = new List<MyItem>();
public List<MyItem> Collection
{
get { return _collection; }
set { _collection = value; InvokePropertyChanged(new PropertyChangedEventArgs("Collection")); }
}
private Thickness _offset1;
public Thickness Offset1
{
get { return _offset1; }
set { _offset1 = value; InvokePropertyChanged(new PropertyChangedEventArgs("Offset1")); }
}
private Thickness _offset2;
public Thickness Offset2
{
get { return _offset2; }
set { _offset2 = value; InvokePropertyChanged(new PropertyChangedEventArgs("Offset2")); }
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Collection.Add(new MyItem("item1", "10"));
Collection.Add(new MyItem("item2", "200"));
Collection.Add(new MyItem("item3", "600000"));
Collection.Add(new MyItem("item4", "1"));
}
private void ScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
//imitate scrolling here
double leftToRight = Offset1.Left;
if (e.ScrollEventType == ScrollEventType.SmallIncrement)
leftToRight += 5;
if (e.ScrollEventType == ScrollEventType.SmallDecrement)
leftToRight -= 5;
Offset1 = new Thickness(leftToRight, 0, 0, 0);
}
private void ScrollBar2_Scroll(object sender, ScrollEventArgs e)
{
//imitate scrolling here
double leftToRight = Offset2.Left;
if (e.ScrollEventType == ScrollEventType.SmallIncrement)
leftToRight += 5;
if (e.ScrollEventType == ScrollEventType.SmallDecrement)
leftToRight -= 5;
Offset2 = new Thickness(leftToRight, 0, 0, 0);
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
public class MyItem
{
public string name { get; set; }
public string price { get; set; }
public MyItem(string n, string p)
{
name = n;
price = p;
}
}
}
In order to attempt to get around the problem I outline in Binding Selected RowCount to TextBlock not Firing OnPropertyChanged after DataGrid Scroll; namely updating a TextBlock with the currently selected row count of a DataGrid when it scrolls. I have attempted to introduce the AttachedCommandBehaviour designed by Marlon Grech [an amazing class structure that allows you to bind commands to specific events of a given control].
Now for the question, using this AttachedCommandBehaviour, how can I get a TextBlock to update based upon a DataGrid's SelectionChangedProperty?
The XMAL is
<Window x:Class="ResourceStudio.Views.AddCultureWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:ResourceStudio.ViewModels"
xmlns:converters="clr-namespace:ResourceStudio.Converters"
xmlns:dataAccess="clr-namespace:ResourceStudio.DataAccess"
xmlns:attachedCommand="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
Title="Add Culture"
Height="510" Width="400"
WindowStartupLocation="CenterOwner"
VerticalContentAlignment="Stretch"
MinWidth="380" MinHeight="295">
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="377*"/>
<ColumnDefinition Width="15*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
<Grid>
<Grid.Resources>
<converters:EnumToBooleanConverter x:Key="enumToBooleanConverter"/>
<Style x:Key="HyperlinkButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="Filters" Grid.Row="0" Margin="0,0,0,5">
<StackPanel VerticalAlignment="Top">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="2,2,2,2">
<RadioButton Content="All Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=AllCultures}"/>
<RadioButton Content="Neutral Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=NeutralCultures}"/>
<RadioButton Content="Specific Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=SpecificCultures}"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Language:" Grid.Column="0"/>
<TextBox HorizontalAlignment="Stretch" Grid.Column="1"
Margin="2,0,2,0" Height="22"/>
</Grid>
</StackPanel>
</GroupBox>
<DataGrid x:Name="cultureDataGrid" Grid.Row="1" AlternatingRowBackground="Gainsboro" AlternationCount="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="True"
CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Extended"
EnableRowVirtualization="True" EnableColumnVirtualization="True"
ItemsSource="{Binding Cultures, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
<DataGrid.Columns>
<DataGridTextColumn Header="Code" Binding="{Binding Code}" IsReadOnly="True"/>
<DataGridTextColumn Header="Language" Binding="{Binding Language}" IsReadOnly="True"/>
<DataGridTextColumn Header="LocalName" Binding="{Binding LocalName}" IsReadOnly="True"/>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FF007ACC"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, IsAsync=True}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
<StackPanel Grid.Row="2" HorizontalAlignment="Right">
<Button Name="button1" Style="{StaticResource HyperlinkButton}"
Focusable="False">
<TextBlock>
<Hyperlink Focusable="False">
Select All
</Hyperlink>
</TextBlock>
</Button>
</StackPanel>
<GroupBox Grid.Row="3" Header="Options">
<CheckBox Content="Copy default values" Margin="3,3"/>
</GroupBox>
<StackPanel Grid.Row="4" Orientation="Horizontal"
HorizontalAlignment="Right" Margin="0,2,0,2">
<Button Content="Select" Width="70" Height="25"
Margin="0,5,5,5" HorizontalAlignment="Right"
VerticalContentAlignment="Center" IsDefault="True"/>
<Button Content="Cancel" Width="70" Height="25"
Margin="5,5,0,5" HorizontalAlignment="Right"
VerticalContentAlignment="Center" IsCancel="True"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
<StatusBar Grid.Row="1" Margin="0,0.4,0.4,-0.4">
<StatusBarItem DockPanel.Dock="Left" Background="#FF007ACC" Margin="0,2,0,0">
<TextBlock Text="{Binding TotalSelectedCultures}" Margin="5,0,0,0" Foreground="White"/>
</StatusBarItem>
</StatusBar>
</Grid>
</DockPanel>
</Window>
My ViewModel is
public class CultureDataViewModel : ViewModelBase
{
public FilterType SelectedFilterType { get; private set; }
public ICollectionView CulturesView { get; private set; }
public MultiSelectCollectionView<CultureViewModel> Cultures { get; private set; }
public CultureDataViewModel()
{
SelectedFilterType = FilterType.AllCultures;
LoadCultures();
}
void OnCultureViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
string IsSelected = "IsSelected";
(sender as CultureViewModel).VerifyPropertyName(IsSelected);
if (e.PropertyName == IsSelected)
this.OnPropertyChanged("TotalSelectedCultures");
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (CultureViewModel cultVm in e.NewItems)
cultVm.PropertyChanged += this.OnCultureViewModelPropertyChanged;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (CultureViewModel cultVm in e.OldItems)
cultVm.PropertyChanged -= this.OnCultureViewModelPropertyChanged;
}
public void LoadCultures()
{
// Fill the Culutres collection...
}
public string TotalSelectedCultures
{
get
{
int selectedCultures = this.Cultures.SelectedItems.Count;
return String.Format("{0:n0} of {1:n0} cultures selected",
selectedCultures,
Cultures.Count);
}
}
}
The link to a downloadable example of the use of the AttachedCommandBehaviour is Here. Your help is most appreciated...
So, as far as I understand, you want to have TextBlock which says:
"5 of 100 items selected"
I've reconstructed your window, the button below the list shows how many items you have selected.
Is that correct? If that is really all you want to do, you don't need to involve the ViewModel at all:
All I did, was this:
<Button Content="{Binding SelectedItems.Count, ElementName=cultureDataGrid}" />
Does this help in some way already or is there a reason for using the pretty complex method with event to command etc.?
EDIT
Your problem basically melts down to "How can I bind to SelectedItems property of a DataGrid. Here is how I solved it:
Define a behavior with a dependency property for the DataGrid, which relays the SelectedItems of the DataGrid to the DependencyProperty:
public class BindableSelectedItems : Behavior<DataGrid>
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof (IList), typeof (BindableSelectedItems), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var grid = ((BindableSelectedItems) sender).AssociatedObject;
if (grid == null) return;
// Add logic to select items in grid
}
public IList SelectedItems
{
get { return (IList) GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var grid = (DataGrid) sender;
SelectedItems = grid.SelectedItems;
}
}
Attach this behavior to the DataGrid and bind the SelectedItems property to a property on your ViewModel:
<DataGrid x:Name="cultureDataGrid" ItemsSource="{Binding Cultures}">
<i:Interaction.Behaviors>
<behaviors:BindableSelectedItems x:Name="CulturesSelection"
SelectedItems="{Binding SelectedCultures, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
with the property on your ViewModel like this:
public IList SelectedCultures
{
get { return _selectedCultures; }
set
{
_selectedCultures = value;
OnPropertyChanged("SelectedCultures");
}
}
If you only want to get the count of selected items, you can bind to the behavior directly and don't need a field on the ViewModel for this:
<TextBlock Text="{Binding Path=SelectedItems.Count, ElementName=CulturesSelection}" Margin="5,0,0,0" Foreground="White"/>
In your ViewModel you can use the SelectedItems property to work with the selection:
var selectedCultures= SelectedCultures.OfType<CultureViewModel>();
I've uploaded the solution, see the link in the comments.
I hope this helps and whish you good luck! There always are a thousand ways to do stuff in WPF and it is really hard to find the best one sometimes...