My Master's Thesis Problems and solutions encountered…

31Oct/100

Cascading Comboboxes in MVVM

Cascading comboboxes refers to having a combobox being populated depending on the selected item of another combobox. In my example, I have what is called a Group Questionnaire, which can contain any number of Questionnaires.

The main purpose with this post is to show how to deal with the Silverlight bug that entails that the binding of the SelectedItem to the second combobox is broken the second time the first combobox is updated, if it is bound to an ObservableCollection.

The actual populating of the Cascading comboboxes is not at all different than when a combobox is populated normally, but I will show it very quickly anyway.

The first thing you need to do is create a new class that will inherit the normal ComboBox:

Step 1: Create the following class in your ViewModel, which inherits the ComboBox.


  public class XComboBox : ComboBox
    {
        private BindingExpression bE;
        public XComboBox()
        {
            this.SelectionChanged += new SelectionChangedEventHandler(XComboBox_SelectionChanged);
        }

        void XComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (bE==null)
            {
               bE = this.GetBindingExpression(ComboBox.SelectedValueProperty);
            }
            else
            {
                if (this.GetBindingExpression(ComboBox.SelectedValueProperty) == null)
                {
                   this.SetBinding(ComboBox.SelectedValueProperty, bE.ParentBinding);
                }
            }
        }

    }

Step 2: In your View, where you want to add the cascading comboboxes, add a reference to the ViewModel, so you can access the XCombobox. Notice that the reference is called NewCombo:


xmlns:NewCombo="clr-namespace:ViewModel;assembly=ViewModel"

Step 3: Add the combobox in the View. Notice the NewCombo:XComboBox:

<!-- FIRST COMBOBOX -->
<NewCombo:XComboBox  x:Name="AllParentQstionnaires"
        ItemsSource="{Binding GroupQuestionnaireCB, Mode=OneWay}"
       SelectedValue="{Binding SelectedGroupQuestionnaireCB, Mode=TwoWay}"
       DisplayMemberPath="ParentQuestionnaireName"
       SelectedValuePath="ParentQuestionnaireID"
                                  />

<!-- SECOND COMBOBOX -->
   <NewCombo:XComboBox  x:Name="AllQuestionnaires"
       ItemsSource="{Binding QuestionnaireCB, Mode=OneWay}"
       SelectedValue="{Binding SelectedQuestionnaireCB, Mode=TwoWay}"
       DisplayMemberPath="QuestionnaireName"
       SelectedValuePath="QuestionnaireID"
                            />

And that's basically it! The rest works as usual, and you will want to create the properties of the ComboBoxes in the ViewModel.

Notice that the two comboboxes are bound to the GroupQuestionnaireCB and the QuestionnaireCB.

Step 4: Create all the properties for the comboboxes, and populate the first in the constructor, and the second combobox in the setter of the first combobox.


using ViewModel;
using ViewModel.QMServiceReference; 

namespace ViewModel
{
    public class Client_Questionnaire3_ViewModel : INotifyPropertyChanged
    {

        QMServiceReference.Service1Client WebService; 

        #region combobox properties

        //First combobox
        private ObservableCollection<ParentQuestionnaire> groupQuestionnaireCB;
        public ObservableCollection<ParentQuestionnaire> GroupQuestionnaireCB
        {
            get
            {
                return groupQuestionnaireCB;
            }
            set
            {

                groupQuestionnaireCB = value;
                RaisePropertyChanged("GroupQuestionnaireCB");

            }

        }

        private int selectedGroupQuestionnaireCB;
        public int SelectedGroupQuestionnaireCB
        {
            get
            {

                return selectedGroupQuestionnaireCB;
            }
            set
            {
                selectedGroupQuestionnaireCB = value;
                //Vi populate combobox nr. 2 with the SelectedValue from combobox 1. 

                RaisePropertyChanged("SelectedGroupQuestionnaireCB");
                //Populate next combobox
                WebService.GetAllQuestionnairesCompleted += new EventHandler<GetAllQuestionnairesCompletedEventArgs>(WebService_GetAllQuestionnairesCompleted);
                WebService.GetAllQuestionnairesAsync(2, SelectedGroupQuestionnaireCB);
            }
        }

        //Second combobox

        private ObservableCollection<AllQuestionnaires> questionnaireCB;
        public ObservableCollection<AllQuestionnaires> QuestionnaireCB
        {
            get
            {
                return questionnaireCB;
            }
            set
            {
                questionnaireCB = value;
                RaisePropertyChanged("QuestionnaireCB");
            }
        }

        private int selectedQuestionnaireCB;
        public int SelectedQuestionnaireCB
        {
            get
            {
                return selectedQuestionnaireCB;
            }
            set
            {
                selectedQuestionnaireCB = value;
                RaisePropertyChanged("SelectedQuestionnaireCB");

            }

        }

        #endregion

        //Contructor

        public Client_Questionnaire3_ViewModel()
        {

                WebService = new Service1Client();
                 //calls webservice to populate the first combobox
                WebService.GetParentQuestionnaireCompleted += new EventHandler<GetParentQuestionnaireCompletedEventArgs>(WebService_GetParentQuestionnaireCompleted);
                WebService.GetParentQuestionnaireAsync(2); 

        }

        void WebService_GetParentQuestionnaireCompleted(object sender, GetParentQuestionnaireCompletedEventArgs e)

     //populates the first combobox
       {
            GroupQuestionnaireCB = e.Result;
            //selects the first value as deafult
            if (GroupQuestionnaireCB.Count > 0)
            {
             SelectedGroupQuestionnaireCB = GroupQuestionnaireCB[0].ParentQuestionnaireID;
            }
        }

        void WebService_GetAllQuestionnairesCompleted(object sender, GetAllQuestionnairesCompletedEventArgs e){
        //populates the second combobox
            QuestionnaireCB = e.Result;
        //selects the first value as default
            if (QuestionnaireCB.Count > 0)
            {
                SelectedQuestionnaireCB = QuestionnaireCB[0].QuestionnaireID;
            }

        }

//Eventhandler
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyname)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyname));
            }
        }

  }

}

And that's it!

Well, I have not shown the whole Model aspect, but it it's completely like described in my previous post.

29Oct/100

Upload Files to MS SQL DB – MVVM

In my application, I needed the users to be able to upload a file in order to document their answer to questions about their working and environmental conditions.

First, I am just going to show the sequence of windows the user has to go through in order to upload a specific file.

1) The users clicks the "Upload file" button
2) The user chooses which file to upload
3) The user is informed about the upload (progress bar)
4) The user is informed of whether the upload succeeded or failed

Step 1: Create the "Upload file" button.


        <Button x:Name="Upload"
            Content="Upload file"
            Command="{Binding UploadFile}"
            HorizontalAlignment="Center"/>

Step 2: Create the UploadFile command that the button is bound. If you have not already created the DelegateCommand.cs needed to handle your commands from buttons, add the following class to your ViewModel:

using System;
using System.Windows.Input;

namespace ViewModel
{
    public class DelegateCommand : ICommand //
    {
        private Predicate<object> _canExecute;
        private Action<object> _method;
        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action<object> method)
            : this(method, null)
        {
        }

        public DelegateCommand(Action<object> method, Predicate<object> canExecute)
        {
            _method = method;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }

            return _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _method.Invoke(parameter);
        }

        protected virtual void OnCanExecuteChanged(EventArgs e)
        {
            var canExecuteChanged = CanExecuteChanged;

            if (canExecuteChanged != null)
                canExecuteChanged(this, e);
        }

        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged(EventArgs.Empty);
        }
    }
}

Otherwise, just create UploadFile command, that defines what needs to be done when the user clicks the button.


        private DelegateCommand uploadFile;
        public DelegateCommand UploadFile // property bound to the button
        {
            get
            {
                if (uploadFile == null)
                    uploadFile = new DelegateCommand(executeuploadFile, canExecuteUploadFile);
                return uploadFile;
            }
        }
 //Defines if the button is clickable.
        private bool canExecuteUploadFile(object parameter)
        {
                return true; //always clickable... for now.
          }

      //Execute
        private void executeuploadFile(object parameter)
        {

            //Provides the dialog box that allows the user to chose files from their computer
            OpenFileDialog openDialog = new OpenFileDialog();
          //New instance of WebService
            WebService = new Service1Client();

           //Open dialog box, where user choses file
           if (openDialog.ShowDialog() == true)
            {
                try
                {
                    using (Stream stream = openDialog.File.OpenRead())
                    {
                        // Don't allow really big files (more than 5 MB).
                        if (stream.Length < 5120000)
                        {
                            byte[] data = new byte[stream.Length];
                            stream.Read(data, 0, (int)stream.Length);

                             //The method that is called after the upload has completed:
                            WebService.UploadFileCompleted += new EventHandler<AsyncCompletedEventArgs>(WebService_UploadFileCompleted);
                            // The method that uploads to the database: An ID, the name of the file, and the file in a bite array.
                            WebService.UploadFileAsync(AID, openDialog.File.Name, data);
                           //Open progress bar that lets the user know the file is being uploaded (window 2).
                             ShowText = "Uploading file";
                            BusyWindow = true;

                        }
                        stream.Close();
                    }

                }
                catch (FileNotFoundException ex)
                {

                }

            }
        }

//The method that is called when the file has been uploaded
  void WebService_UploadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            //Close busyWindow
            BusyWindow = false;

//Show verification childwindow. 

        }

Remember that you have to make changes to the .config files on both the client and the server, in order to allow larger files to be sent.

Step 4: Be sure to have a table in your database, where you can upload the files: You'll need a table with the following 3 columns: An Int attribute to store the unique value, a varchar(50) for the name of the file, and a varbinary(MAX) for containing the bite array that constitutes the file.

Step 5: I am not going show how to add the [ServiceContract] etc. in the Model, as it has been showed many times before, so I am just showing the UploadFile() that is defined in the Model:


 public class FileDTO_DAC : DBConnection
    {

        public void UploadFile(int a_ID, string FileType, byte[] Data)
        {
            //Open SQL connection
            SqlConnection dbConn = connectToDB();

         try
            {
                dbConn.Open();
                using (SqlCommand cmd = new SqlCommand("INSERT INTO attachments (a_ID, fileName, fileBin) VALUES (@a_ID, @Type, @BinaryData)", dbConn))
                {
                    cmd.Parameters.Add("@a_ID", SqlDbType.Int).Value = a_ID;
                    cmd.Parameters.Add("@Type", SqlDbType.VarChar, 50).Value = FileType;
                    cmd.Parameters.Add("@BinaryData", SqlDbType.VarBinary).Value = Data;
                    cmd.ExecuteNonQuery();
                }
             }
            catch (Exception)
            {
                throw;
            }
            dbConn.Close();

Enjoy!

Filed under: C#, MVVM, Silverlight 4 No Comments
29Oct/100

Download files from a MS SQL Database – MVVM

This post will show to download a file that is located in a MS SQL Database. The user goes through the following steps:

1) The user clicks the download button
2) The user is informed that the download is in progress (progress bar)
3) The user has to confirm the download
4) The user is asked to define where he want the file to be saved
5) The user is of the success or failure of saving the file to his desktop

The most important thing to notice is that the user has to 'confirm' the download twice: When he first clicks the button, and again when it has been downloaded, and the user can chose to 'Save' or 'Cancel' to open the dialog box, where the user determines where on his desktop he want to save the file.

Step 1. Create the object that is returned from the database containing the file you want to download. The file object has two attributes: The name of the file, and the file itself, which is saved as a bite array, and is of type SqlBytes:

using System.Data.SqlTypes;

  [DataContract]
    public class FileDTO
    {

        private string _fileType;
        private SqlBytes _data;

        [DataMember]
        public string FileType
        {
            get { return _fileType;}
            set { _fileType = value; }
        }

        [DataMember]
        public SqlBytes Data
        {
            get  {return _data;}
            set  {_data = value; }
        }
    }

Step 2: While you're in the Model, create the method GetFile() that returns an instance of the FileDTO class, that contains the file from the database.


 public FileDTO GetFile(int ID)
        {

            //New instance
            FileDTO fileDTO = new FileDTO();
            SqlConnection dbConn = connectToDB();
            //The query
            string _selectQuery = string.Format("Select * from attachments where a_ID = " + ID + "");
          try
            {
                dbConn.Open();
                using (SqlCommand cmd = new SqlCommand("Select * from attachments where a_ID = @ID", dbConn))
                {
                    cmd.Parameters.Add("@ID", SqlDbType.Int).Value = ID;
                    SqlDataReader rdr = cmd.ExecuteReader();
                    while (rdr.Read())
                    {
                        fileDTO.FileType = rdr.GetString(2);
                        fileDTO.Data = rdr.GetSqlBytes(3);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }

            dbConn.Close();
            return fileDTO;
        }

Step 3: Create the "Download file" button in the View. Notice that it binds to the command DownloadFile

<Button x:Name="Get" Content="Download file"
                Style="{StaticResource Knap}"  Width="80"
                Command="{Binding DownloadFile}"
  />
 

Step 4: In the ViewModel, create the command that executes when the "Download File" button is clicked, and that calls the GetFile() method described in step 2.

You will have to notice several things: First, the executeDownloadFile calls the GetFile(), and when the call is completed, the WebService_GetFileCompleted method is called, and return value (the file) is saved in a newly created object of type FileDTO , i.e. the class you created in step 1.


//Property bound to the button
 private DelegateCommand downloadFile;
        public DelegateCommand DownloadFile
        {
            get
            {
                if (downloadFile == null)
                    downloadFile = new DelegateCommand(executeDownloadFile, canExecuteDownloadFile);
                return downloadFile;
            }
        }

//Defines if the button is clickable
        private bool canExecuteDownloadFile(object parameter) //Definerer om knappen er klik-bar.
        {
                return true; //always clickable
        }

        private void executeDownloadFile(object parameter)
        {
           //Shows progress bar while downloading
           ShowText = "Downloading file";
            BusyWindow = true;

            WebService = new Service1Client();
            //defines the method that is called when it returns with a value
            WebService.GetFileCompleted += new EventHandler<GetFileCompletedEventArgs>(WebService_GetFileCompleted);
             //Calls the method
            WebService.GetFileAsync(AID);

        }
         //Method is called when the Model has returned with a value
        void WebService_GetFileCompleted(object sender, GetFileCompletedEventArgs e)
        {
            // Call the method CallBackFromService with the result (the file) as a parameter
           CallBackFromService(e.Result);
        }
        //Create new instance of FileDTO.
        FileDTO fileDTO;
        public void CallBackFromService(FileDTO fileDTOCallBack)
        {
            Set to the local version of the fileDTO, close progressbar
            fileDTO = fileDTOCallBack;
            BusyWindow = false;

            //Makes button from (3) visible: The user has to confirm that he wants to download the file.
            AfterDownload = Visibility.Visible;
 }

Step 6: After the CallBackFromService has been called, the user must confirm that he wants to download the file. The button in "Save File" in window (3) shown above is bound to the command SaveAttachment:

     //Property bound to the button
        private DelegateCommand saveAttachment;
        public DelegateCommand SaveAttachment
        {
            get
            {
                if (saveAttachment == null)
                    saveAttachment = new DelegateCommand(executeSaveAttachment);
                return saveAttachment;
            }
        }
 //Execute:
        private void executeSaveAttachment(object parameter)
        {
            //Enables the apperance of a Dialog, where the user can specify where to save the file
            SaveFileDialog textDialog = new SaveFileDialog();

            //We found out what type of file it is (.doc, .pdf, etc). 

            //name of file, for example MyFile.doc
            string test = fileDTO.FileType;
            //Gets the number of characters before the "."
            int index = test.LastIndexOf(@".");
            //Find the type of the file by retrieving the characters after the index number
            string docType = test.Substring(index);
            //Doctype : .doc, .pdf, etc. 

           //Knowing the type, we can now define which default type-value has to be chosen in the Dialog, where the user saves the file. 

            if (docType == ".jpg")
            {

                textDialog.DefaultExt = ".jpg";
                textDialog.Filter = "JPG|*.jpg";

            }

            else if (docType == ".docx")
            {
                textDialog.DefaultExt = ".docx";
                textDialog.Filter = "Microsoft Office 2008 .docx|*.docx";

            }

            else if (docType == ".doc")
            {
                textDialog.DefaultExt = ".doc";
                textDialog.Filter = "Microsoft Office 2003 .doc|*.doc";

            }

//10 other formats have been added

             //save the file in a bite array
            byte[] fileBytes = fileDTO.Data;//your bytes here
            //Open dialog where the user determines where to save the file.
             bool? result = textDialog.ShowDialog();
            if (result == true)
            {
                When the user clicks OK, the file is saved.
                using (Stream fs = (Stream)textDialog.OpenFile())
                {
                    fs.Write(fileBytes, 0, fileBytes.Length);
                    fs.Close();
                }
            }

            //The user is informed of the download.
            AfterRealDownload = Visibility.Visible;

And that's it!

Filed under: C#, MVVM, Silverlight 4 No Comments