locked
Using OpenFIleDialog in MVVM Pattern RRS feed

  • Question

  • In my experiment, I was able to using code behind to open File Dialog window to select an image and update to database using the following code:

     private void btnUpload_Click(object sender, RoutedEventArgs e)
            {
                OpenFileDialog OpenFileDialog = new OpenFileDialog();
                if (OpenFileDialog.ShowDialog() == true)
                {
                    FileStream fs = OpenFileDialog.File.OpenRead();
                    var bt = new byte[fs.Length];
                    fs.Read(bt, 0, int.Parse(fs.Length.ToString()));
                    (dfProductAdd.CurrentItem as Product).ProdPicImg = bt;
                }
            }


    Now in my real project, I use MVVM approach.  I managed to bind the upload button to command and send message from VM to code beind to trigger to File Dialog window but I don't know how to send back the uploaded image to VM.  Can someone help?  Thanks.

    Monday, January 3, 2011 10:39 AM

Answers

  • So where do I put this chunk of code in, VM and code behind?

    A simple version of my approach would look something like this:


    private void btnUpload_Click(object sender, RoutedEventArgs e)
    {
      OpenFileDialog OpenFileDialog = new OpenFileDialog();
      if (OpenFileDialog.ShowDialog() == true)
      {
        using(FileStream fs = OpenFileDialog.File.OpenRead())
        {
          var vm = DataContext as MyViewModel;
          vm.SaveImageCommand.Execute(fs);
        }
      }
    }

    In the view model, there would be some kind of RelayCommand (or a similar implementation, depending on your MVVM framework) that just relays to a method where the actual logic happens:


    private void SaveImage(Stream fs)
    {
      var bt = new byte[fs.Length];
      fs.Read(bt, 0, bt.Length);
      CurrentProduct.ProdPicImg = bt;
    
      // etc.
    }

    Edit: 


    Can I post part of my code and you guys point me out how to correct?  Appreciated.

    Yes sure, you can do that too if you're still struggling.


    Monday, January 3, 2011 5:49 PM

All replies

  • Hi

    Call a Command or set an Property (imagename or image) from the VM.

    [Edit] You get the VM when you cast the DataContext to your VM class. If you don't want, that your View knows the exact VM type, use a Interface to define the imagename property . Implement this interface in the VM and  cast the DataContext in View to this Interface [/Edit]

    Monday, January 3, 2011 3:23 PM
  • Hi. Here is my personal opinion about this problem: in my eyes, people often tend to "over-MVVM" in this particular scenario. At the moment you:

    • Bind your button to a command.
    • From your command send a message back to the view.
    • In the view open a file dialog and read the image.
    • Send the image back to the view model by setting a property or using another command (as correctly proposed by swo).

    The question for me is why you need the first two steps at all. Let's look at it again: In your original implementation, you had the code to open and read the file in code behind, in an event handler for a button click. In your new, supposedly more MVVM'ish implementation, you have the code to open and read the file in code behind, in an event handler for a message received event. What do you gain by that? It makes your code more convoluted due to the extra round-trip between your view model and view, harder to understand for others, and I don't see any advantage.

    Here is what I do in that situation: 

    • Handle the button click event in code-behind like you did initially.
    • Show the file dialog.
    • Pass back the FileStream to the view model, using a command (with the same considerations swo was mentioning).

    My view model stays clean, because it does not rely on any specific UI or know about the view. All it wants is a stream. The view model also stays testable. I can easily pass in another stream from somewhere else if I need to unit test the command. Should some clever view designer find a different way to get the file from the user in another way (of course that's not possible in SL), the view could be switched without affecting the view model at all.



    Monday, January 3, 2011 3:52 PM
  • How do you invoke a command for the viewmodel in the code behind?  (I usuallly invoke commands through a UI binding)

    Monday, January 3, 2011 4:44 PM
  • Hi

    pseudocode:

    var dc =  this.DataContext as yourViewmodle; // or IYourIterface //
    dc.YourCommand.Execute(parameter);

    Monday, January 3, 2011 4:50 PM
  • Hi Peter,

    Thanks for you opinion.  I don't really care whether it's MVVM or not.  The only reason is just because the example I reference to is more comprehensive in comparision with other.  This is my 1st SL project.  Unforunately even MVVM there are numbers of variation to code (base on my knowledge).  Also there are not many end-to-end LOB out there.  Seriously I'm a bit regret to use SL however I was really attacted by the compelling small examples.  I am already far behind the schedule.  I can only catch up by keeping search for good example and do my own experiments to undertstand.

    Monday, January 3, 2011 5:20 PM
  • Hi

    pseudocode:

    var dc =  this.DataContext as yourViewmodle; // or IYourIterface //
    dc.YourCommand.Execute(parameter);

    Thanks sow.

    So where do I put this chunk of code in, VM and code behind?  What is the command and parameter in this case?  Sorry if this question is too stupid.

    Monday, January 3, 2011 5:25 PM
  • Can I post part of my code and you guys point me out how to correct?  Appreciated.

    Monday, January 3, 2011 5:26 PM
  • So where do I put this chunk of code in, VM and code behind?

    A simple version of my approach would look something like this:


    private void btnUpload_Click(object sender, RoutedEventArgs e)
    {
      OpenFileDialog OpenFileDialog = new OpenFileDialog();
      if (OpenFileDialog.ShowDialog() == true)
      {
        using(FileStream fs = OpenFileDialog.File.OpenRead())
        {
          var vm = DataContext as MyViewModel;
          vm.SaveImageCommand.Execute(fs);
        }
      }
    }

    In the view model, there would be some kind of RelayCommand (or a similar implementation, depending on your MVVM framework) that just relays to a method where the actual logic happens:


    private void SaveImage(Stream fs)
    {
      var bt = new byte[fs.Length];
      fs.Read(bt, 0, bt.Length);
      CurrentProduct.ProdPicImg = bt;
    
      // etc.
    }

    Edit: 


    Can I post part of my code and you guys point me out how to correct?  Appreciated.

    Yes sure, you can do that too if you're still struggling.


    Monday, January 3, 2011 5:49 PM
  • Appreciated, Peter.  Here is what I did:

    main page xmal

    <Button Content="Add" Height="26" x:Name="btnAdd" Width="75" HorizontalAlignment="Right" 	Command="{Binding AddProductCommand}" />


    main page code behind

    Messenger.Default.Register<LaunchAddProductMessage>(this, OnLaunchAddProduct);
    
     private void OnLaunchAddProduct(LaunchAddProductMessage msg)
            {
                var ProdAdd = new ProdAdd();
                var ProdAddVM = new ProdAddVM();
                ProdAdd.Closed += new EventHandler(ProdAdd_Closed);
                ProdAdd.DataContext = ProdAddVM;
                ProdAddVM.NewProduct = msg.Product;
                ProdAdd.Show();
            }
    
            void ProdAdd_Closed(object sender, EventArgs e)
            {
                ProdAdd ProdAdd = (ProdAdd)sender;
                if (ProdAdd.DialogResult == true)
                {
                   // researching
    
                }         
            }

    main page VM

    public RelayCommand AddProductCommand { get; set; }   
     private void OnAddProduct()
            {
                Product NewProduct = new Product();
                Messenger.Default.Send(new LaunchAddProductMessage() { Product = NewProduct });
            }


    child window xmal

    ..
    <TextBox x:Name="ItemCode" Text="{Binding NewProduct.ProdCode, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue='', ValidatesOnExceptions=true}"  Grid.Column="0" Margin="0,1,0,1"/>
    ..
     <Button x:Name="btnUpload"  Content="Upload" Width="75" Height="28" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,2,0" Command="{Binding UploadCommand}"/>


    child window code behind

    Messenger.Default.Register<OpenFileDialogMessage>(this, OnOpenFileDialog);
    ..
     private void OnOpenFileDialog(OpenFileDialogMessage msg)
            {
                 OpenFileDialog OpenFileDialog = new OpenFileDialog();
                if (OpenFileDialog.ShowDialog() == true)
                {
                    FileStream fs = OpenFileDialog.File.OpenRead();
                    var bt = new byte[fs.Length];
                    fs.Read(bt, 0, int.Parse(fs.Length.ToString()));
                    msg.Product.ProdImg = bt;   
                }
            }

    child window VM

    ..
    public RelayCommand UploadCommand { get; set; }
    ..
    UploadCommand = new RelayCommand(OnUpload);
    ..
     private void OnUpload()
            {  
                Messenger.Default.Send(new OpenFileDialogMessage() { Product = NewProduct });
            }
    ..
    




     

    Monday, January 3, 2011 6:34 PM
  • So where do I put this chunk of code in, VM and code behind?

    A simple version of my approach would look something like this:


    private void btnUpload_Click(object sender, RoutedEventArgs e)
    {
      OpenFileDialog OpenFileDialog = new OpenFileDialog();
      if (OpenFileDialog.ShowDialog() == true)
      {
        using(FileStream fs = OpenFileDialog.File.OpenRead())
        {
          var vm = DataContext as MyViewModel;
          vm.SaveImageCommand.Execute(fs);
        }
      }
    }

    In the view model, there would be some kind of RelayCommand (or a similar implementation, depending on your MVVM framework) that just relays to a method where the actual logic happens:


    private void SaveImage(Stream fs)
    {
      var bt = new byte[fs.Length];
      fs.Read(bt, 0, bt.Length);
      CurrentProduct.ProdPicImg = bt;
    
      // etc.
    }


    I followed this coding but got compilation error "Argument 1: cannot convert from 'method group' to System.Action' on this line

    SaveImageCommand = new RelayCommand(OnSaveImage);

    Any idea what's wrong with it?

    Monday, January 3, 2011 7:30 PM
  • Hi

    I don't know what kind of RelayCommand you use but i guess the delegate should look simmilar like

    private void SaveImage(Object fsObject)
    {
     var fs = fsObject as Stream; 

     var bt = new byte[fs.Length];
      fs.Read(bt, 0, bt.Length);
      CurrentProduct.ProdPicImg = bt;

      // etc.

     

    Monday, January 3, 2011 8:07 PM
  • So where do I put this chunk of code in, VM and code behind?

    A simple version of my approach would look something like this:


    private void btnUpload_Click(object sender, RoutedEventArgs e)
    {
      OpenFileDialog OpenFileDialog = new OpenFileDialog();
      if (OpenFileDialog.ShowDialog() == true)
      {
        using(FileStream fs = OpenFileDialog.File.OpenRead())
        {
          var vm = DataContext as MyViewModel;
          vm.SaveImageCommand.Execute(fs);
        }
      }
    }

    In the view model, there would be some kind of RelayCommand (or a similar implementation, depending on your MVVM framework) that just relays to a method where the actual logic happens:


    private void SaveImage(Stream fs)
    {
      var bt = new byte[fs.Length];
      fs.Read(bt, 0, bt.Length);
      CurrentProduct.ProdPicImg = bt;
    
      // etc.
    }


    I followed this coding but got compilation error "Argument 1: cannot convert from 'method group' to System.Action' on this line

    SaveImageCommand = new RelayCommand(OnSaveImage);

    Any idea what's wrong with it?

    I googled and found a sample.  Now it's compilable.  Now the database field is populated.  Only change I made is the following:

    public RelayCommand<Stream> SaveImageCommand { get; set; }
    


     

    Monday, January 3, 2011 8:40 PM
  • Hi again. Yes, that is the generic version of RelayCommand, you can use it for strongly-typed arguments. Glad you found it out already and that it's working now.

    Tuesday, January 4, 2011 1:19 AM
  • Yes Peter, it is working now.  Thanks a lot.  Without you folks' help, I can't imagine how long is going to get this issue fixed.  Salute.

    Tuesday, January 4, 2011 10:16 AM