IanHacker
IanHacker

Reputation: 581

How to show the filenames under the selected folder on the Radio Button

I'd like to to show the filenames under the folders selected on the Radio Button. However, my program shows only the filenames under the last folder. Even when the selection is changed, the filenames don't change.

I've tried some LINQ statements, but none of them worked. How can I fix this? Here's my entire code:
EDIT: Made the code shorter and added Show_Button_Click()

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace Folder_Reader
{
    public class Question
    {
        public string QuestType { get; set; }
        public string Label { get; set; }
        public ObservableCollection<Answer> Answers { get; set; }
    }

    public class Answer : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged([CallerMemberName]string propertyName = null)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private string _Ans;
        public string Ans
        {
            get => _Ans;
            set
            {
                if (_Ans == value)
                    return;
                _Ans = value;
                RaisePropertyChanged();
                RaisePropertyChanged(nameof(FullName));
            }
        }
        public string FullName => $"{Ans}";
        public bool IsSelected { get; set; }
    }

    public partial class MainWindow : Window
    {
        public ObservableCollection<Question> Questions { get; set; }

        List<string> ToBeMergedList = new List<string>();

        public static readonly DependencyProperty QuestionProperty =
            DependencyProperty.Register("Question", typeof(Question), typeof(MainWindow),
                new FrameworkPropertyMetadata((Question)null));

        public Question Question
        {
            get { return (Question)GetValue(QuestionProperty); }
            set { SetValue(QuestionProperty, value); }
        }

        public MainWindow()
        {
            InitializeComponent();
            RunMerge();
        }

        private void RunMerge()
        {
            string dataPath = @"C:\Data\";
            DirectoryInfo[] folders = GetFoldernames(dataPath);

            FillInClass();

            string typePath = null;
            StringBuilder sb = new StringBuilder();

            foreach (var (q, toBeSelectedFolder) in from q in Questions
                                                    from toBeSelectedFolder in folders
                                                    select (q, toBeSelectedFolder))
            {
                sb.AppendLine($"{ toBeSelectedFolder }");
                q.Answers.Add(new Answer() { Ans = toBeSelectedFolder.ToString() });

                // This line finally puts the LAST folder into typePath, but I don't know how to fix this
                typePath = toBeSelectedFolder.FullName.ToString();
                //typePath = q.Answers.Where(a => a.IsSelected).Select(a => a.Ans).ToString();
            }

            DataContext = this;

            string searchPattern = "*log*.xlsx";
            FileInfo[] files = GetFilenames(typePath, searchPattern);

            List<Datalog> DatalogList = new List<Datalog>();
            AddDatalogList(DatalogList, files);
            MyListView.ItemsSource = DatalogList;
        }

        private void FillInClass()
        {
            Questions = new ObservableCollection<Question>()
            {
                new Question()
                {
                    QuestType = "Radio",
                    Label = "Type",
                    Answers = new ObservableCollection<Answer>()
                }
            };
        }

        private static DirectoryInfo[] GetFoldernames(string path)
        {
            DirectoryInfo di = new DirectoryInfo(path);
            DirectoryInfo[] folders =
                di.GetDirectories("*", SearchOption.TopDirectoryOnly);
            return folders;
        }

        private static FileInfo[] GetFilenames(string path, string searchPattern)
        {
            DirectoryInfo di = new DirectoryInfo(path);
            FileInfo[] files =
                di.GetFiles(searchPattern, SearchOption.AllDirectories);
            return files;
        }

        private static void AddDatalogList(List<Datalog> DatalogList, FileInfo[] files)
        {
            var duplicateGroups = files.GroupBy(file => file.Name)
                                       .Where(group => group.Count() > 1);

            foreach (var group in duplicateGroups)
            {
                DatalogList.Add(new Datalog() { Merge = false, Filename = group.First().FullName });
            }

            var singleGroups = files.GroupBy(file => file.Name)
                               .Where(group => group.Count() == 1);

            foreach (var group in singleGroups)
            {
                foreach (var file in group)
                {
                    DatalogList.Add(new Datalog() { Merge = false, Filename = file.FullName });
                }
            }
        }

        private void CheckBox_Checked(object sender, RoutedEventArgs args)
        {
            ToBeMergedList.Add((sender as CheckBox).Content.ToString());
        }

        private void CheckBox_Unchecked(object sender, RoutedEventArgs args)
        {
            ToBeMergedList.Remove((sender as CheckBox).Content.ToString());
        }

        private void Show_Button_Click(object sender, RoutedEventArgs e)
        {
            foreach (Question q in Questions)
            {
                Console.WriteLine(q.Label + " : "
                    + string.Join(", ", q.Answers.Where(a => a.IsSelected).Select(a => a.Ans)));
                MessageBox.Show(q.Label + " : "
                    + string.Join(", ", q.Answers.Where(a => a.IsSelected).Select(a => a.Ans)));
            }
        }

        private void Merge_Button_Click(object sender, RoutedEventArgs e)
        {
            StringBuilder sb = new StringBuilder();

            foreach (var toBeMergedFile in ToBeMergedList)
            {
                sb.AppendLine($"{ toBeMergedFile }");
            }
            MessageBox.Show(sb.ToString());
        }
    }
}

MainWindow.xaml

<Window x:Class="Folder_Reader.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:Folder_Reader"
        mc:Ignorable="d"
        Title="Merge Tool" Height="500" Width="450">
    <Window.Resources>
        <DataTemplate x:Key="RadioQuestion">
            <ItemsControl ItemsSource="{Binding Answers}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <RadioButton Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}" 
                        GroupName="{Binding DataContext.Label, 
                        RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </DataTemplate>
    </Window.Resources>
    <Grid RenderTransformOrigin="0.5,0.5">
        <Grid.RowDefinitions>
            <RowDefinition Height="150*"/>
            <RowDefinition Height="421*"/>
        </Grid.RowDefinitions>
        <Border BorderThickness="2">
            <ScrollViewer VerticalScrollBarVisibility="Auto" Margin="10,0,0,0">
                <StackPanel>
                    <ItemsControl ItemsSource="{Binding Questions}" Grid.IsSharedSizeScope="True">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <TextBlock Text="{Binding Label}"/>
                                    <ContentControl x:Name="ccQuestion" Grid.Column="1" 
                                                    Content="{Binding}" Margin="10"/>
                                </Grid>
                                <DataTemplate.Triggers>
                                    <DataTrigger Binding="{Binding QuestType}" Value="Radio">
                                        <Setter TargetName="ccQuestion" Property="ContentTemplate" 
                                                Value="{StaticResource RadioQuestion}"/>
                                    </DataTrigger>
                                </DataTemplate.Triggers>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
            </ScrollViewer>
        </Border>
        <ListView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                  Name="MyListView" Margin="0,0,0,40" Grid.Row="1">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="To-Be-Merged File">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox Margin="5,0" IsChecked="{Binding Merge}" Content="{Binding Filename}"  
                                      Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
        <Button Content="Show" HorizontalAlignment="Right" VerticalAlignment="Bottom"
                Margin="0,0,252.2,10.4" Width="100" Click="Show_Button_Click" Grid.Row="1" Height="21"/>
        <Button Content="Merge" HorizontalAlignment="Right" VerticalAlignment="Bottom" 
                Margin="0,0,73.6,10.4" Width="100" Click="Merge_Button_Click" Grid.Row="1" Height="21"/>
    </Grid>
</Window>

Datalog.cs

namespace Folder_Reader
{
    public class Datalog
    {
        public bool Merge { get; set; }

        public string Filename { get; set; }
    }
}

Let's assume that there are three folders:

In this case, 7_, 8_, 9_log.xlsx will be shown, even when FolderA is selected. I expect to see 1_, 2_, 3_log.xlsx when I select FolderA on the Radio Button.

Upvotes: 0

Views: 265

Answers (1)

pete the pagan-gerbil
pete the pagan-gerbil

Reputation: 3166

The line:

FileInfo[] files = GetFilenames(typePath, searchPattern);

is using the typePath from the last iteration of the loop, which only has files 7, 8 & 9 in it. The list isn't databound and isn't refreshed based on the selection so it'll never show anything different.

Add the files to the answer object (new property), and bind the file list to the selected item. It would mean generating the Datalog objects for all files and adding them to the Answer object, as that Answer is being added to the list (q.Answers.Add(...)). Then instead of adding only the few files to the ListView control (which only happens once), you would bind it's ItemSource property to the selected answers 'Files' property.

EDIT

I've had to make quite a few changes to the original code to make this work. It's not perfect by any means, but hopefully it indicates the right direction to go in for you.

  1. First of all, your file lists aren't connected to Answers in any way. I've added a property of List<Datalog> called Files to the Answer class to hold those, and populated it from the RunMerge method.

  2. The ListView needs to be bound to whatever the selected answer is. This is fairly easy, it's a small tweak to the XAML:

    <ListView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
              Name="MyListView" Margin="0,0,0,40" Grid.Row="1"
              DataContext="{Binding SelectedAnswer}" ItemsSource="{Binding Files}">
    
  3. The RunMerge method needs to be reordered slightly to populate the Files property in the Answers, as they are constructed. I've also populated a TopLevel property in the Answer with the main view model class, as we'll need a reference to that later. Some of the other methods now need to return where they didn't before (like AddDatalogList and FillInClass), but there's nothing complicated about that - I leave it to you.

    private void RunMerge()
    {
        string dataPath = @"C:\Data\";
        DirectoryInfo[] folders = GetFoldernames(dataPath);
    
        var order = FillInClass();
    
        string typePath = null;
        StringBuilder sb = new StringBuilder();
        string searchPattern = "*log*.xlsx";
        var viewModel = new MainWindowViewModel
        {
            Order = order
        };
    
        foreach (var (q, toBeSelectedFolder) in from q in order.Questions
                                                from toBeSelectedFolder in folders
                                                select (q, toBeSelectedFolder))
        {
            sb.AppendLine($"{ toBeSelectedFolder }");
            typePath = toBeSelectedFolder.FullName.ToString();
            q.Answers.Add(new Answer()
            {
                TopLevel = viewModel,
                Ans = toBeSelectedFolder.ToString(),
                Files = AddDatalogList(new List<Datalog>(), GetFilenames(typePath, searchPattern))
            });
        }
    
        DataContext = viewModel;
    }
    
  4. Binding to the MainWindow as your DataContext makes things messy. I bound it instead to a MainWindowViewModel class that exposes two properties, Order and SelectedAnswer. It looks like this:

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
        internal void SelectedAnswerChanged()
        {
            RaisePropertyChanged(nameof(SelectedAnswer));
        }
    
        public Order Order { get; set; }
    
        public Answer SelectedAnswer
        {
            get { return Order.Questions.First().Answers.FirstOrDefault(x => x.IsSelected); }
        }
    }
    

The SelectedAnswerChanged method in there is quite important. When a user clicks on the radio button, the TwoWay binding that you had there changes the underlying data model (Answer class). However, the SelectedAnswer property doesn't know to trigger a PropertyChanged event based on that so the ItemsSource of your ListView won't change. So in the setter of the IsSelected property in the Answer class, we can call the SelectedAnswerChanged method to trigger the PropertyChanged event from the correct place, which in turn triggers the binding in the ListView to pick up a new ItemsSource... now it is updating the ListView as the user makes a selection. The updated Answer class looks like this:

public class Answer : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private string _Ans;
    public string Ans
    {
        get => _Ans;
        set
        {
            if (_Ans == value)
                return;
            _Ans = value;
            RaisePropertyChanged();
            RaisePropertyChanged(nameof(FullName));
        }
    }
    public string FullName => $"{Ans}";
    private bool _isSelected;
    public bool IsSelected 
    { 
        get
        {
            return _isSelected;
        }
        set
        {
            _isSelected = value;
            RaisePropertyChanged(nameof(IsSelected));
            TopLevel.SelectedAnswerChanged();
        }
    }
    public List<Datalog> Files { get; set; }
    public MainWindowViewModel TopLevel { get; internal set; }
}

Hopefully you can follow what's going on here (and that I didn't miss any pieces out). Separating your models from your window would make it simpler to read and modify and I'd recommend reading up on the MVVM pattern. There are more changes that I could/would make if this were my project, but I wanted to keep the minimum work to help illustrate what needs to be added to make it work.

Hope this helps :)

Upvotes: 1

Related Questions