3

I am trying to generate a list from a database that I have and add it to a listbox in WPF C# with LINQ.

This is the XAML I have for now:

<ListView x:Name="ListBox" Margin="16,232,22,10.4" SelectionMode="Multiple">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Nettstasjon" Width="100" DisplayMemberBinding="{Binding Path=Name}"/>
                <GridViewColumn Header="Område" Width="100" DisplayMemberBinding="{Binding Path=Area}"/>
                <GridViewColumn Header="Radial" Width="100" DisplayMemberBinding="{Binding Path=Radial}"/>
            </GridView>
        </ListView.View>
    </ListView>

And to read the database I have this codebehind. I made a class "TransformerStation" to hold the data per line.

public class TransformerStation : INotifyPropertyChanged
    {
        public string Name { get; set; }
        public string Radial { get; set; }
        public string Area {get; set; }

        public event PropertyChangedEventHandler PropertyChanged;
    }

Then I made a class to read the database

public IEnumerable<TransformerStation> ReadCSV(string fileName, string radial)
    {

        string[] lines = File.ReadAllLines(System.IO.Path.ChangeExtension(fileName, ".csv"), Encoding.UTF8);


        return lines.Select(line =>
        {
            string[] data = line.Split(';');

            foreach (var value in data)
            {
                if (data = radial)
                {
                    return new TransformerStation(data[0], data[1], data[2]);
                }
            }     
        });
    }

My database looks like this:

N765;TANGEN;TANGEN L98
R2351;SPIKKESTAD;SPIKKESTAD K88
S622;KRÅKSTAD;KRÅKSTAD L812
S1318;KRÅKSTAD;KRÅKSTAD L812

What I need is as follows:

I have a WPF with butttons with content that is equal to the third column in my database(F.eks KRÅKSTAD L812). I want, when I push a button, to collect all the items in column one from my database, that matches the button content in column three, and show them in a listbox that I have created above.

So, if I push the button with content "Kråkstad L812" then my list will show

S622
S1318

I am sorry, I am really new at C# and WPF. So I dont have much code to show. But I really appreciate all the help I can get and it really makes a difference if you explain what is happening in the code :) So just to clarify, I basically need the SELECT radial FROM database and generate a list from that.

EDIT, I am also open to other ways of importing the data from the csv database. I have just picked one that worked for me :)

Thomas
  • 305
  • 1
  • 3
  • 11

3 Answers3

3

Hard to give an complete answer since not all your code is given, and you might want to refactor some of it later as you develop you complete project. So I might offer some advice to get you going farther along. You will need to search/research some of these things, but since you are new to c#/wpf, you were planning on this anyway, right? :)

...fix the == like @wentimo pointed out...

For returning IEnumerable, you might look at yield.

It looks like you might be reading your database many times. If it is changing all the time, that might be fine. If it is more static, consider reading it once and storing it in a Dictionary where Name is the key and TransformerStation is a value. Then for each lookup you can can use .Where clause to filter.

See this post and read the answers to learn more. Maybe this is relevant:

var keysForValues = dictionary.Where(pair => values.Contains(pair.Value))
                          .Select(pair => pair.Key);

There is no one best way...research Dictionary, List, List<Tuple, etc.

Some Sample Code (create in Console project)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace ConsoleApplication1
{
    [DebuggerDisplay("{Name} {Radial} {Area}")]
    public class TransformerStation
    {
        public string Name { get; set; }
        public string Radial { get; set; }
        public string Area { get; set; }

        public TransformerStation(string name, string radial, string area)
        {
            Name = name;
            Radial = radial;
            Area = area;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            CSVToDictionary();
        }

        private static void CSVToDictionary()
        {
            var dictionary = new Dictionary<string, TransformerStation>();

            // your csv
            string[] lines = new string[] {
            "N765; TANGEN; TANGEN L98",
            "R2351; SPIKKESTAD; SPIKKESTAD K88",
            "S622; KRÅKSTAD; KRÅKSTAD L812",
            "S1318; KRÅKSTAD; KRÅKSTAD L812"};

            foreach (var line in lines)
            {
                var data = line.Split(';');
                dictionary.Add(data[0], new TransformerStation(data[0].Trim(), data[1].Trim(), data[2].Trim()));
            }

            var find = "KRÅKSTAD L812";
            var search = dictionary.Values.Where(v => v.Area == find);

            foreach (var found in search)
            {
                Console.WriteLine($"Matches: {found.Name} {found.Radial} {found.Area}");
            }
        }
    }
}

What you should learn from this

  • DebuggerDisplay (use debugger to see a nicer representation of your data)
  • Simulating reading your csv and storing in dictionary
  • Trimming spaces from input data (you never trust data read in, always verify/clean)
  • Filtering data using linq
  • Outputting results using String interpolation

Good luck with your project.

Community
  • 1
  • 1
Kory Gill
  • 6,993
  • 1
  • 25
  • 33
  • Thank you for the advice. I am definitely interested in seeing how to store the database in a Dictionary. Could you provide some example code? :) – Thomas Dec 31 '15 at 22:33
  • added example code to further educational purpose of my answer – Kory Gill Dec 31 '15 at 23:05
3

Quite a few problems here. First of all you're not actually implementing INotifyPropertyChange support correctly. Probably the easiest thing to do here is to add MVVM Lite to your project (it'll only take a minute if you use NuGet) and base your view models off ViewModelBase:

public class TransformerStation : ViewModelBase
{
    private string _Name;
    public string Name
    {
        get { return this._Name; }
        set { this._Name = value; RaisePropertyChanged(); }
    }

    private string _Radial;
    public string Radial
    {
        get { return this._Radial; }
        set { this._Radial = value; RaisePropertyChanged(); }
    }

    private string _Area;
    public string Area
    {
        get { return this._Area; }
        set { this._Area = value; RaisePropertyChanged(); }
    }

    public TransformerStation(string name, string radial, string area)
    {
        this.Name = name;
        this.Radial = radial;
        this.Area = area;
    }

}

The second thing I notice is that you seem to be filtering your data when you load it, which suggest you're re-loading every time the user pressed a button. Unless you have a really large amount of data you may as well keep the whole thing stored in memory and filter it after it's been loaded. A dictionary will not only make the filtering process faster it will also provide you with a list of all radials in the data set, should you require that at runtime for creating your buttons dynamnically:

    public Dictionary<string, List<TransformerStation>> ReadCSV(string fileName)
    {

        string[] lines = File.ReadAllLines(System.IO.Path.ChangeExtension(fileName, ".csv"), Encoding.UTF8);
        return lines.Select(line =>
        {
            string[] data = line.Split(';');
            return new TransformerStation(data[0], data[1], data[2]);
        })
        .GroupBy(ts => ts.Radial)
        .ToDictionary(g => g.Key, g => g.ToList());
    }

Back in your MainViewModel you'll need to load the data and store it somewhere (i.e. AllStations), you'll need a property like "SelectedRadial" which you set in response to the user pressing buttons, and another ("CurrentStations") which is the filtered version of AllStations:

    private Dictionary<string, List<TransformerStation>> _AllStations;
    public Dictionary<string, List<TransformerStation>> AllStations
    {
        get { return this._AllStations; }
        private set { this._AllStations = value; RaisePropertyChanged(); }
    }


    private string _SelectedRadial;
    public string SelectedRadial
    {
        get { return this._SelectedRadial; }
        set
        {
            this._SelectedRadial = value;
            RaisePropertyChanged();
            this.CurrentStations = this.AllStations[value];
        }
    }

    private List<TransformerStation> _CurrentStations;
    public List<TransformerStation> CurrentStations
    {
        get { return this._CurrentStations; }
        private set { this._CurrentStations = value; RaisePropertyChanged(() => this.CurrentStations); }
    }

    public ICommand RadialCommand {get { return new RelayCommand<string>(OnRadialCommand); }}
    private void OnRadialCommand(string radial)
    {
        this.SelectedRadial = radial;
    }


    public MainViewModel()
    {
        this.AllStations = ReadCSV(@"data.csv");
    }

Then it's just a matter of binding some minimal XAML to generate the buttons (again, that's optional) and display your data:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <ListBox ItemsSource="{Binding AllStations.Keys}">
        <ListBox.ItemTemplate>
            <ItemContainerTemplate>
                <Button Command="{Binding Path=DataContext.RadialCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" CommandParameter="{Binding}">
                    <TextBlock Text="{Binding}" />
                </Button>
            </ItemContainerTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    <ListView Grid.Column="1" ItemsSource="{Binding CurrentStations}" >
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Nettstasjon" Width="100" DisplayMemberBinding="{Binding Path=Name}"/>
                <GridViewColumn Header="Område" Width="100" DisplayMemberBinding="{Binding Path=Area}"/>
                <GridViewColumn Header="Radial" Width="100" DisplayMemberBinding="{Binding Path=Radial}"/>
            </GridView>
        </ListView.View>
    </ListView>

</Grid>

Result:

enter image description here

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • Thank you very much for the reply and the time! I have tried to implement the code you pasted, but I get a few errors: RaisePropertyChanged(), gives me an error "Does not exist in current context" ColumnDefinition was not found in type "Grid" As well as ViewModelBase "The type or namespace ViewModelBase could not be found (are you missing a using directive..." – Thomas Dec 31 '15 at 23:36
  • and I did add the MVVM lite toolkit as far as I know – Thomas Dec 31 '15 at 23:41
  • Is there a `using GalaSoft.MvvmLight;` at the top of the source file? – Mark Feldman Dec 31 '15 at 23:44
  • `ColumnDefinitions` is definitely part of `Grid`, you didn't spell it `ColumnDefinition` did you? – Mark Feldman Dec 31 '15 at 23:47
  • I figured out the Grid issue, but the RaisePropertyChanged() still gives an error. And yes, I am using GalaSoft.MvvmLight :) – Thomas Jan 01 '16 at 00:26
  • So long as you're correctly deriving from ViewModelBase for both your MainViewModel and TransformerStation classes the only other thing I can think of is an older version of .NET, in which case try specifying the property explicitly each time you call RaisePropertyChanged i.e. `private set { this._AllStations = value; RaisePropertyChanged(() => this.AllStations); }`. If that doesn't work then you'll have to zip up your project so I can have a look at it. – Mark Feldman Jan 01 '16 at 00:33
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99471/discussion-between-thomas-and-mark-feldman). – Thomas Jan 01 '16 at 15:47
1

Seems like your if statement

if (data = radial)

Should be:

if (data == radial)

Because = is an assignment and == is an equals operation.

Besides that, I guess you want something like: If user input exists somewhere in the database.. return the matches.

If so, use IndexOf instead of == like so:

if (data[0].IndexOf(radial, StringComparison.OrdinalIgnoreCase) >= 0)
{
    // return new ..
}

Though the above only checks for data[0]. You could use the foreach loop like you have now to compare all data in row. Or use:

if (data.Any(dataPart => dataPart.IndexOf(radial, StringComparison.OrdinalIgnoreCase) >= 0)
{
    // return new ..
}
Measurity
  • 1,316
  • 1
  • 12
  • 24
  • 2
    I wanted to point out that in the question that data is a string array and radial is a string, the first half of answer won't compile – wentimo Dec 31 '15 at 21:12
  • 2
    @Measuring there are lot more problems here. data is `string[]` and radial is a `string`, `TransformerStation` doesn't have that constructor, not all paths of `Select` return a result, `INotifyPropertyChanged` is not properly implemented, `ListBox.ItemsSource` is not set – dkozl Dec 31 '15 at 21:15
  • @dkozl Yes I know it's not working code. But it seems like he got his stuff working mostly so I wasn't bothering that. – Measurity Dec 31 '15 at 21:17
  • @dkozl, like I said. I am very new to C#, so I am sure there are plenty off errors and better ways to do things. I just hope people give me pointers and explain things so that I can improve them. Thanks for the help :) – Thomas Dec 31 '15 at 21:54