-1

I have been trying to move my project to MVVM for some time.

There is a DataTable I am getting from Database:

MainProcess.cs:

    public static DataTable CustomersInLiinos = new DataTable();

    public static void MergedTable()
    {
        var t1 = ConnectAndRetriveDatatatableS(); // t1
        var t2 = ConnectAndRetriveDatatatableF(); // t2

        CustomersInLiinos = t1.Copy();
        CustomersInLiinos.Merge(t2);
    }

ViewModel.cs:

    private async Task ExecuteLoadMainTableDataAsync(object commandParameter)
    {
        if (MainProcess.CheckForVPNInterface())
        {
            if (MainProcess.CustomersInLiinos != null)
            {
                this.HasProgress = true;

                IEnumerable<Item> resultItems = await LoadMainTableDataAsync();
                this.Items = new ObservableCollection<Item>(resultItems);
                EnableItemsFiltering();

                this.HasProgress = false;
            }
        }
        else
        {
            throw new VpnInterfaceException("Please, check your VPN connection!");
        }
    }

Inside ViewModel.cs I have also this:

    public Task<DataView> LoadMainTableDataAsync()
    {
        return Task.Run(() =>
        {
            MainProcess.MergedTable();

            return MainProcess.CustomersInLiinos.DefaultView;
        });
    }

Curently I am having an error pointing at await LoadMainTableDataAsync();:

Severity Code Description Project File Line Suppression State Error CS0266 Cannot implicitly convert type 'System.Data.DataView' to 'System.Collections.Generic.IEnumerable<Liinos_inspector_FilterTest.Item>'. An explicit conversion exists (are you missing a cast?)

I understand that there is an error in LoadMainTableDataAsync? I am loading data to DataView and should load to IEnumerable instead?

Would it be easier to utilize this:

    public class JoinedFandS
    {
       public string YRNRO { get; set; }
       public string HAKUNIMI { get; set; }
       public string NIMIA { get; set; }
       public string NIMIB { get; set; }
    }

    public static IEnumerable<JoinedFandS> GetMyJoinedResult()
    {
        var t1 = ConnectAndRetriveDatatatableS(); // t1
        var t2 = ConnectAndRetriveDatatatableF(); // t2

        var firstTable = ...

        var secondTable = ...

        var results = firstTable.Concat(secondTable);

        return results;
    }

EDIT:

<Window x:Class="Liinos_inspector_FilterTest.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:Liinos_inspector_FilterTest="clr-namespace:Liinos_inspector_FilterTest" 
        mc:Ignorable="d"
        Title="Liinos database inspector" Height="672" Width="1000" Icon="Images/logo_icon-small.jpg" Background="White" MinWidth="1000">

  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

Severity Code Description Project File Line Suppression State Error XLS0414 The type 'ViewModel' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built. Liinos inspector FilterTest MainWindow.xaml 11

and

Severity Code Description Project File Line Suppression State Error XDG0008 ViewModel is not supported in a Windows Presentation Foundation (WPF) project. Liinos inspector FilterTest MainWindow.xaml 11

What should I use instead of ViewModel?

10101
  • 2,232
  • 3
  • 26
  • 66
  • We could do with you xaml as well. Is MainProcess some sort of static global variable? (You should avoid that). You don't need to return DefaultView in `LoadMainTableDataAsync`. All you need is to return DataTable, or better yet create a Model and use that instead. – XAMlMAX Sep 07 '20 at 08:22
  • 1
    This is a follow up question. I think you should've ask the proper question with proper context before. This question also lacks a lot of context to be answered properly. You should have added the context of your previous question. Solving this error will break the solution of your previous question. – BionicCode Sep 07 '20 at 08:34
  • 1
    Hey, thank you very much for starting a bounty on this question to honor my answer with some extra reputation points. I really appreciate your gesture. Thank you very much. That's very very kind of you. – BionicCode Sep 10 '20 at 15:10
  • 1
    @BionicCode There were many questions, I didn't know which one to select. Thank you very much for your patient and help! I am a little bit wiser now again, but there are a lot of things to learn still. I am doing programming on my free-time besides my regular job. However I hope one day I will reach that level of actually giving advice's to others =)) – 10101 Sep 10 '20 at 15:18

1 Answers1

1

This is a data type mismatch. You cannot press a DataView into a collection. It's also more practicable to store the DataTable instead of the DataView. It's cheaper to get the view from the table, than getting the table from the view (in case you need to operate on the DataTable later).
Fixing this error also needs fixing the filtering.

DataGrid can handle a DataTable directly.

Note that you must await the return value of LoadMainTableDataAsync. Otherwise this method would return prematurely (because the result is computed in a background thread). I am sure, that this part of you code doesn't even compile. Maybe this is just example code.

This example also adjusts the filtering based on the new data structure. To filter a DataTable.DataView you must use the DataView.RowFilter property (see Expression Syntax for help). You need to adjust the actual filter logic to your requirements:

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
  public ICommand LoadMainTableDataCommand => new RelayCommand(async param => ExecuteLoadMainTableDataAsync());

  private DataTable mainDataTable;   
  public DataTable MainDataTable 
  {
    get => this.mainDataTable;
    set 
    { 
      this.mainDataTable = value; 
      OnPropertyChanged();  
      
      // Set the DataTable filter expression
      EnableRowFiltering();
    }
  }

  // Binding source for the first name TextBox
  private string firstNameSearchKey;   
  public string FirstNameSearchKey
  {
    get => this.firstNameSearchKey;
    set 
    { 
      this.firstNameSearchKey = value; 
      OnPropertyChanged();  
      
      // Refresh the DataTable filter expression
      EnableRowFiltering();
    }
  }

  // Binding source for the last name TextBox
  private string lastNameSearchKey;   
  public string LastNameSearchKey
  {
    get => this.lastNameSearchKey;
    set 
    { 
      this.lastNameSearchKey = value; 
      OnPropertyChanged();  
      
      // Refresh the DataTable filter expression
      EnableRowFiltering();
    }
  }

  private bool hasProgress;   
  public bool HasProgress
  {
    get => this.hasProgress;
    set 
    { 
      this.hasProgress = value; 
      OnPropertyChanged();
    }
  }

  public void EnableRowFiltering()
  {
    // The filter assumes a column 'FirstName' and a column 'LastName' in the DataView. 
    // The filter expression mimics string.StartsWith.
    this.MainDataTable.DefaultView.RowFilter = 
      $"FirstName LIKE '{this.FirstNameSearchKey}*' " + 
      $"OR LastName LIKE '{this.LastNameSearchKey}*'";
  }

  private async Task ExecuteLoadMainTableDataAsync()
  {
    if (MainProcess.CheckForVPNInterface())
    {
      if (MainProcess.Customers != null)
      {
        this.HasProgress = true;

        this.MainDataTable = await LoadMainTableDataAsync();

        this.HasProgress = false;
      }
    }
    else
    {
      throw new VpnInterfaceException("Please, check your VPN connection!");
    }
  } 

  public async Task<DataTable> LoadMainTableDataAsync()
  {
    return await Task.Run(() =>
    {
      MainProcess.MergedTable();

      return MainProcess.CustomersInLiinos;
    });
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <Window.Resources>
    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
  </Window.Resources>
 
  <StackPanel>
    <ProgressBar IsIndeterminate="True"
                 Visibility="{Binding HasProgress, Converter={StaticResource BooleanToVisibilityConverter}}" />

    <Button Command="{Binding LoadMainTableDataCommand}" 
            Content="Load Data" />

    <TextBox Text="{Binding FirstNameSearchKey}" />
    <TextBox Text="{Binding LastNameSearchKey}" />
    <DataGrid ItemsSource="{Binding MainDataTable}" />
  <StackPanel>
</Window>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thank you again! I think I am quite close to something that will hopefully work so I can continue my learning curve. Currently I have two errors left `this.MainDataTable.DataView.RowFilter` saying `DataTable' does not contain a definition for 'DataView' and no accessible extension method 'DataView' accepting a first argument of type 'DataTable' could be found ` and `new RelayCommand(ExecuteLoadMainTableDataAsync)` I guess I need new class for that. I have created it but probably made something wrong. – 10101 Sep 07 '20 at 09:12
  • 1
    You are welcome. I've used the wrong (non-existing) property. It must be `DefaultView` instead. I have updated the example. For the `RelayCommand` you can copy [this](https://learn.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern#relaying-command-logic) implementation from Microsoft and use `new RealyCommand(async param => await ExecuteLoadMainTableDataAsync())`. Or alternatively add a constructor overload that accepts a `Func`. – BionicCode Sep 07 '20 at 09:20
  • Looks like this can be removed as well `object commandParameter`? Now there are no errors. I will try to test. – 10101 Sep 07 '20 at 09:34
  • No this can't be removed. The `RelayCommand` expects a `Action` where `object` is the `commandParameter`. You can add an constructor overload to `RelayCommand that takes an `Action` and then you can remove the parameter `commandParameter`. Let me know if you need more help. – BionicCode Sep 07 '20 at 09:53
  • True that! I just couldn't understand how to solve this issue as `public ICommand LoadMainTableDataCommand => new RelayCommand(async param => await ExecuteLoadMainTableDataAsync());` is waiting for argument inside brackets but there no any. – 10101 Sep 07 '20 at 09:58
  • Ah, you are right. Yes you can remove the parameter from the `ExecuteLoadMainTableDataAsync`. I forgot that it is passed to the `RelayCommand` as Lambda expression. Just remove the `object commandParameter`. You were right. – BionicCode Sep 07 '20 at 10:01
  • It was used as Method Group before. I have updated the answer. – BionicCode Sep 07 '20 at 10:02
  • I can't thank you enough for your help! However I was thinking maybe you can give me a hint. I don't have any errors now but on button click nothing is happening. I also don't see any issues in Bindings. Everything seems to be logically connected. I have disconnected VPN and after clicking a button "Load Data" I don't receive any warning message. This makes me think that there might some problem with button Binding as nothing is happening? – 10101 Sep 07 '20 at 12:36
  • I don't think it's the binding. Maybe your `MainProcess` doesn't retunr anything. Plaeas set a breakpoint at the first line of `ExecuteLoadMainTableDataAsync` and run the application in debugging mode. When clicking the button, the debugger should break there. If it doesn't then indeed the binding wouldn't resolve. – BionicCode Sep 07 '20 at 12:45
  • If the debugger breaks continue to set deeper into the method. There are a couple of if-statements. Please check if they all evaluate to `true`. Or alternatively set a breakpoint at the first line of `LoadMainTableDataAsync` to see if all if-statements are `true`. – BionicCode Sep 07 '20 at 12:47
  • I have tried setting breakpoints in different places but button click does nothing. By clicking it nothing seems to be happen. I have also tried some simple solution from here https://stackoverflow.com/questions/12422945/how-to-bind-wpf-button-to-a-command-in-viewmodelbase and added MessageBox to `public void MyAction()` but nothing seems to happen either on button click... I don't understand what is wrong. – 10101 Sep 07 '20 at 14:17
  • 1
    Can check if the view model that defines the command is in the `DataContext` of your view? How do you set the DataContext? – BionicCode Sep 07 '20 at 14:31
  • Ah, I forgot to add this part to XAML. Now there are some more errors in XAML. I have updated my question. I will try to find something on Google – 10101 Sep 07 '20 at 14:47
  • I have added ` ` and now it compiles and button seems to be working! At least I am getting VPN connection exception – 10101 Sep 07 '20 at 15:06
  • I was just about to write that you forgot to qualify the type with its namespace. – BionicCode Sep 07 '20 at 15:07
  • Do you expect this exception? – BionicCode Sep 07 '20 at 15:07
  • Yes, now button works correct. However Data is not loaded to DataGrid. I have tested several breakpoints and it looks like everything works fine. Data is loaded from Database to Datatable. Datatables are merged together but for some reason Data does not populate to DataGrid. I will investigate further tomorrow and maybe ask a new question if I will not be able to solve it myself. – 10101 Sep 07 '20 at 15:41
  • Thanks anyway! I got some progress and learned something new today! – 10101 Sep 07 '20 at 15:41
  • 1
    I have updated my answer. I guess the problem is that the `MainDataTable` property is changing, but since the auto-property wasn't raising the `INotifyPropertyChanged.PropertyChanged` event, the `DataGrid` binding never updates. I have now turned the property into a property with change notification. Everything should work now. There were so many things refactored during the course of this question that some errors slipped because I forgot certain code to adjust. I apologize for that. Let me know if there are still any issues, but there shouldn't. – BionicCode Sep 07 '20 at 16:39
  • WOW! It actually works now! This is my first MVVM attempt... I have tried during the spring without success and now with your help it actually happened! Thank you a million times! I have now one dream less =)) There is something with Filters still, but I will continue my investigations later. I will drop some bounty once it will be available! – 10101 Sep 07 '20 at 16:50