3

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...

Community
  • 1
  • 1
MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • Unless I missed it, you Xaml doesn't show anything being wired up to an attached behaviour. I don't know Marlon's class (I write my own attached behaviours with a snippet), but the normal approach is to wire the control's event (for you that's SelectionChanged) to an ICommand in your ViewModel *VIA* the dependency property. I can post an example of an attached behaviour doing that if it's of any use, but it wouldn't be from Marlon's class, just a plain attached behaviour. – Gayot Fow Jul 10 '13 at 01:34

1 Answers1

9

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.

Capture

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...

Marc
  • 12,706
  • 7
  • 61
  • 97
  • Hi Marc. In this dialog I am selecting a range of cultures to create. I populate the `DataGrid` with `public MultiSelectCollectionView Cultures`. Note, I am using a custom class I have created to get around the problem I highlight in http://stackoverflow.com/q/17459832/626442. I have done it the way I have illustrated based upon `cultureViewModel.IsSelected` property as I thought this was the correct way, but this fails when the view is scrolled. The method you outline may-well be enough as long as when I get the CultureViewmodels that are selected the counts match... – MoonKnight Jul 10 '13 at 12:08
  • ... which they might not do if the `OnCultureViewModelPropertyChanged` event in the ViewModel class above is not being fired? Again, I can't tell you how much you help means as you seem to be my only source of help on the journey to learn WPF and MVVM! All the very best. – MoonKnight Jul 10 '13 at 12:09
  • @Killercam Hi again, no problem, I'm glad to help and know how difficult it is to get started with all this stuff on your own! My general impression is that your approach is too complicated. I'm not sure from what I can tell from here, but I think you can simplify a lot if you distribute the tasks between View and ViewModel differently... Is it possible for you to upload the project? – Marc Jul 10 '13 at 17:27
  • If you could let me know when you have downloaded I will remove the code... Thanks again. – MoonKnight Jul 10 '13 at 19:29
  • @Killercam I've downloaded it and have a look at it as soon as I can. See you later. – Marc Jul 11 '13 at 08:31
  • @Killercam https://incasoftware-my.sharepoint.com/personal/marc_branscheid_inca-studio_com/Documents/Shared%20with%20Everyone/ResourceStudio.zip – Marc Jul 12 '13 at 08:09
  • thanks so much. That is so so nice of you to take time to explain this. Absolute class, I will award you some rep in due course... One thing is that I can access your link? Also, is the way I had it in the example okay as it seems to work okay. That is, in the `TextBlock` bind to the `DataGrid` selected items, and use `` to get a two way binding to the view model? Perhaps I should wait to see your code. Thanks again! :] – MoonKnight Jul 12 '13 at 08:29
  • 1
    The thing is, that you can delete a lot of stuff in your ViewModels, if you just bind to the SelectedItems. Have a look at the code, it should do the same as before, just with less code. But all in all, your code looks good, you'll manage ;) – Marc Jul 12 '13 at 08:36
  • one last thing with this. I have had to implement this in the end due to not being able to update the selected items from my ViewModel. Now I want to be able to set the first row as 'selected' when the for is first shown - how would I go about doing this? – MoonKnight Jul 23 '13 at 22:40
  • Sorry Marc, I meant when the Window/form is first shown. Currently, nothing is selected when the window is shown and if I click on a button I have wired up to select all items, then `SelectedCultures` is null. If I click on an item, _then_ select all, it works fine. Sorry to bother you again... – MoonKnight Jul 24 '13 at 08:32
  • Hi @Killercam, sorry, lot to do these days... Probably best if you upload the solution again. I cant really tell from here. – Marc Jul 26 '13 at 06:12
  • Hi! I've downloaded it. I will take care it doesn't get anywhere else. I will have a look at it asap. Cheers! – Marc Jul 26 '13 at 10:07
  • Thanks Marc. Again, it is appreciated... For you it should be a very easy problem :]. Sorry to bother you again man. – MoonKnight Jul 26 '13 at 12:52
  • @Killercam I've had a look at it, sorry that it took so long. Let me know when you're available, I'll upload the file then... – Marc Jul 29 '13 at 07:32
  • Hi Marc, again thanks and please don't apologise, what you are doing is really nice of you. I am progressing really well with the application now but it is still little things like this one that I get stumped over how bet to approach. Again thank you... – MoonKnight Jul 29 '13 at 08:22
  • 1
    Hi Killercam, no problem, I'm glad to help. Here's a solution, try whether it works for you: https://dl.dropboxusercontent.com/u/14429255/ResourceStudio130726_RevMarc.zip. The behavior I've implemented last time is basically designed to work one way, i.e. to push the selected items to the ViewModel, but not the other way around. Now, I have implemented a command on the behavior, which simply selects all items, without using the ViewModel at all. Furthermore I've deleted the IsSelected property on the CultureViewModel, because it's obsolete now, and there were some bugs when checking whether – Marc Jul 29 '13 at 08:35
  • 1
    items are selected. See how it goes, as far as I can tell it does the job.. Cheers, mate, nice to hear that you're progressing, keep going ;) – Marc Jul 29 '13 at 08:36
  • Hi Marc, I hope your well mate. I have progressed the app really well. I am having some issues getting find to work as I want. The app is now sufficiently complex as to prevent me asking a question here (it would just be miles too long). Would you be happy to take a look again? I will provide 100+ rep for one of the questions you have answered of your choice (I am also willing to discuss the rep you require). The problem is that I want a way to select either `ResourceName`, `ResourceStringList` so that I can select a cell which has met the find criteria using the find replace dialog. – MoonKnight Oct 08 '13 at 20:41
  • @Killercam Hi, good to hear you're progressing. Thanks for the rep offer, I could use some rep here http://stackoverflow.com/questions/19131916/questions-on-mef-strategy-and-structure/19137819#19137819 ;) Upload your project and I'll have a look at it! – Marc Oct 09 '13 at 09:26
  • Hi marc, thanks. I have looked at the question, but because it has not been accepted I can't award a bounty. I will upvote for now and award a 150 rep bounty when it is accepted... Will upload shortly. Thanks very much for your help. – MoonKnight Oct 09 '13 at 10:25
  • True, you can also take this one: http://stackoverflow.com/questions/15830008/mvvm-and-collections-of-vms/15831128#15831128 – Marc Oct 09 '13 at 10:27
  • It 200 okay with you? – MoonKnight Oct 09 '13 at 10:51
  • Perfectly fine, no worries! – Marc Oct 09 '13 at 10:53
  • If this was a normal grid/column set I would be confident that I could do this (as your teaching has been great) but I am still unsure how to extend your control the way I need. Again, thanks so much for your time. I am still not sure what I am going to do with this software when it is finished (target an existing market or make is Freeware) but I would be willing to consider you as an Author as you have helped greatly... I guess we will see. :] – MoonKnight Oct 09 '13 at 11:19
  • Hey, looks nice! I can't open resources, though. After opening one of the files, nothing really happens... Do you have an idea, I don't have the time to debug right now, unfortunately? – Marc Oct 09 '13 at 14:50
  • Hi Killercam, still doesn't work. There seems to be something going on asynchronously. After selecting the file to open, the 'Load Resource Set' menu item is disabled for a couple of seconds... – Marc Oct 09 '13 at 15:35
  • Hi Marc, really confused about this. I have now tested of Windows Vista and Windows 8. Both fine. Sorry to waste your time here. One thing the application now does is write to `Users\\AppData\Roaming` for settings etc. I will investigate further but it is tough as I am struggling to reproduce. Anyway, I will let you know when I think I have a solution... – MoonKnight Oct 09 '13 at 19:03
  • Pinned Files and recent files are written in the user folder and contain nicely valid XML, I don't think this is the issue. I'll see whether I can get some more infos tomorrow. Cheers. – Marc Oct 09 '13 at 19:19
  • Thanks, mate! I was out of office today, I'll let you know what I found out tomorrow or Saturday! Cheers! – Marc Oct 10 '13 at 20:15
  • No problem, this problem is an annoying one as i have looked into it in dept over the last few nights after work - I can't reproduce it! Nightmare! Sorry for this. You are not able to help me with my actual problem as the app does not work - very poor! If you want any more rep for your time just ask, I would not have gotten this far with out your continued help. All the very best mate. – MoonKnight Oct 11 '13 at 08:43
  • Hi @Killercam! I've been sick, it took a bit longer... I still don't know why it doesn't work, but here's some additional info: I get a StringFormatException on startup, no usable StackTrace, it occurs in Window.Show(), inside MetroContentControl. The exception is handled, so no problem, probably. Loading files seems to work, it must be an UI issue. If I open a file, workspaces are added behind the scenes, just nothing shows up. Maybe this helps you to find the issue...? I'll keep investigating, just a little short in time right now... Cheers! – Marc Oct 15 '13 at 14:52
  • Thanks Marc, sorry to hear you have been unwell, I hope you are feeling better. I have been working hard on the application over the last week or so; I have another update to provide to you. There is no rush with my latest question as there are still a number of other things for me to be getting on with. When you have some time, and I provide a working version(!) you can try the find facility, I basically want to extend this so that I can select individual cells in the data grid. Let me know when you are okay to down load the latest version and try and help, if you want more rep let me know! – MoonKnight Oct 15 '13 at 15:44
  • Hi again, good to here you're progressing! Nevermind because it's not working right now, that's quite normal for a work in progress, as far as I can tell ;) I'm ready for download and have a look at it asap... – Marc Oct 15 '13 at 15:46
  • Hi Killercam, looks great and works fine, the issue is resolved. Loading resources from the start page doesn't work, but you probably know... I've tried the live search feature and it works as I would expect it to. Individual cells are highlighted (green). I noticed, that the search string must be at the start of the cell string, though: When searching 'Foo': 'Foo Bar' is a match 'Bar Foo' is no match. What behavior are you trying to achieve beyond that? Looks awesome so far! – Marc Oct 15 '13 at 19:17
  • I think I will refine the exact 'active search' behavior later. Currently, I am using this to help with the localisation of a large WinForms application - so it works fine for now. The find, as you will notice only works on the row, but not individual cells, this is something I would like you to look at as it is tied to the DataGrid control you kindly authored for the project. I am aiming to create a great looking app that rivals/exceed that of http://www.zeta-resource-editor.com/index.html. – MoonKnight Oct 15 '13 at 19:26
  • Morning. Your app does look better than zeta, definitely. I don't really know what you mean... In the latest version, individual cells are highlighted when there's a match between the search string and the cell value. I've uploaded a screenshot here, just in case it behaves differently than you'd expect: https://dl.dropboxusercontent.com/u/14429255/gidwithselection.PNG. Or did I misunderstand you? – Marc Oct 16 '13 at 07:49
  • This search is fine. User CTRL + F to launch the Find/Replace dialog. You will see that searches using this dialog only stop on rows - it would be nice to highlight individual cells like in the (quick) search you have shown. The problem is, although in my search code (in the Find/Replace dialog) I do search through the cell _values_ (via the ResourceViewModels) I can't highlight those values as individual cells. Thanks Marc. – MoonKnight Oct 16 '13 at 08:17
  • While your on the case, one (potentially small) issue that I have not been able to resolve is enabling the addition of a new resource if you click on the last empty row in the data grid (beware - this action currently crashes the app). I will be addressing this in due course but as this is backed by your DataGrid, perhaps you could take a quick look and advise on the way this could be done. Again, if you want rep for this I will happily provide it - just ask! :] – MoonKnight Oct 16 '13 at 08:21
  • Marc, new version is uploaded this has an options dialog. The theme for the application gets changed (as you will see), but the theme for the main `DataGrid` does not change. I think this is due to it being a custom control. I thought I would update you so you have the latest version. Again, any help you can provide is appreciated. Can I ask you background and whether you have any experience with WebDevelopment? – MoonKnight Oct 17 '13 at 19:11
  • Hi!, maybe we should switch to mail? – Marc Oct 18 '13 at 06:41
  • That's cool with me. I was thinking that you have help a lot and if you wanted to become an author of the software then that's cool. My email is nfcamus gmail.com. If not that is also fine... – MoonKnight Oct 18 '13 at 07:57