Search code examples
c#wpfxamlmvvm

Can a XamlWriter read textbox content when using MVVM?


I have been searching everywhere for this. Just as the title says, is there a way for a XamlWriter to read the content of a text box, as well as the definition of the text box itself?

The text boxes are not directly entered into the Xaml, as they have been binded to the MVVM pattern, at runtime. At the moment, the XamlWriter simply saves the text boxes without their content.

Example code saving the view, but without text box content:

string mystrXAML = XamlWriter.Save(StackPanelContent);

        string name = txtname.Text;

        FileStream fs = File.Create(String.Format(@"C:\Desktop\" + name + ".txt"));
        StreamWriter sw = new StreamWriter(fs);
        sw.Write(mystrXAML);
        sw.Close();
        fs.Close();

EDIT:

Have spent alot of time thinking and researching how to do this still cant find the right answer. This is my XAML code and its still not saving the text box content and this is because they are binded (Is what i researched)

XAML:

<ListView ItemsSource="{Binding Title}" ItemTemplate="{DynamicResource Template}">
     <ListView.ItemContainerStyle>
            <Style>
                <Setter Property="FrameworkElement.Margin" Value="20,20,0,0"/>
            </Style>
     </ListView.ItemContainerStyle>
            <ListView.Resources>
                <DataTemplate x:Key="Template">
                   <TextBox Text="{Binding .}" Width="200" HorizontalAlignment="Left" />
                </DataTemplate>
            </ListView.Resources>
</ListView>

<ListView x:Name="QuestionList" ItemsSource="{Binding Question}" ItemTemplate  {DynamicResource Template}">
      <ListView.ItemContainerStyle>
            <Style>
                <Setter Property="FrameworkElement.Margin" Value="40,20,0,0"/>
            </Style>
      </ListView.ItemContainerStyle>
            <ListView.Resources>
                <DataTemplate x:Key="Template">
                    <TextBox Text="{Binding .}" Width="200" HorizontalAlignment="Left"/>
                </DataTemplate>
            </ListView.Resources>
</ListView>

Solution

  • In WPF, we work with data, not UI controls. So rather than save the TextBoxes with all their extra properties that are meaningless to you, you should save the just text in the collections data bound to the TextBoxes.

    However, if your heart is set on doing this rather than the correct way, then may I draw your attention to the XamlWriter Class page on MSDN:

    The serialization enabled by this method has a series of limitations. This is because the serialization enabled is explicitly run-time, and does not have access to possible design-time information in the original XAML (if any). For details, see Serialization Limitations of XamlWriter.Save.

    And from the linked page on MSDN:

    Common references to objects made by various markup extension formats, such as StaticResource or Binding, will be dereferenced by the serialization process. These were already dereferenced at the time that in-memory objects were created by the application runtime, and the Save logic does not revisit the original XAML to restore such references to the serialized output. This potentially freezes any databound or resource obtained value to be the value last used by the run-time representation, with only limited or indirect ability to distinguish such a value from any other value set locally.

    I've highlighted a section that basically says that yes, you can save the property values of the controls using the XamlWriter.Save method. However, I can't tell you why your values aren't being saved.

    Now when you said that you had searched everywhere, you seem to have missed the most obvious search terms for your problem: XamlWriter Class. For future reference, had you searched with these words, then you'd have found all this out in the top result.

    However, it just makes far more sense to save the data and not the UI controls. Think about it... you already have the mechanism to generate/display these TextBoxes from your collections, so why wouldn't you just use that? Save the data in a database, or a text file and when you reload it, use your existing code to re-populate the TextBoxes from the collections. It really is as simple as populating a collection in your view model with the loaded data. You could even save the data collection with almost the same code that you're using already, or less even.

    That is how it is done properly. Now you can either accept that and move on with your project in the correct manner, or you can continue to ignore this advise and to ask more virtually identical questions. Right now, I think that this is at least your 3rd question asking how to store XAML in a file or database. You could have saved everyone including yourself a lot of time if you had just accepted the good advise that was given to you previously, or even done a proper search.

    Apart from anything else, it's just so much easier persisting the data instead of the UI controls.


    UPDATE >>>

    The linked pages from MSDN show us that the XamlWriter can save data bound data, but unfortunately, I can't tell you why yours is not working.

    To be honest, saving strings is not really enough. You need to save all of the relevant data. The simplest way to display, edit and save this data is to create custom classes that implement the INotifyPropertyChanged interface. For example, for you scenario, I would create a Question class something like this:

    [Serializable]
    public class Question // Implement INotifyPropertyChanged correctly here
    {
        public string Title { get; set; }
        public string Text { get; set; }
        public string Answer { get; set; }
    }
    

    If your questions are multiple choice, then you could do something like this:

    [Serializable]
    public class Question // Implement INotifyPropertyChanged correctly here
    {
        public string Title { get; set; }
        public string Text { get; set; }
        public List<Answer> Answers { get; set; }
        public Answer CorrectAnswer { get; set; }
    }
    

    Of course, you should have private fields backing those public properties... this is just a simplified example. Note the use of the SerializableAttribute... this will enable you to save the data with just a few lines of code.

    You can structure the properties however you want, but the idea is that you encapsulate all of the related data into one class. Then you can provide a DataTemplate for that class type, let's call it Template, and define a collection of controls that will display the relevant property values.

    Now here's the good part... you can create a collection of that type in a view model...:

    public ObservableCollection<Question> Questions { get; set; }
    

    ... and set the ItemTemplate property of any collection control to your DataTemplate:

    <ListBox ItemsSource="{Binding Questions}" ItemTemplate="{StaticResource Template}" />
    

    and then every Question item in the collection will magically be rendered exactly as you defined in the DataTemplate. So to answer your question about how do you reload the data, you just re-populate the Questions collection in the view model and the WPF templating system will correctly re-populate the UI for us in exactly the same way as it did before that data was saved.

    Now as you can see... I've spent a great deal of time explaining this all to you. I trust that you can answer any further questions that you may have with your new found search skills.

    To start you off, here are some useful articles:

    Serialization (C# and Visual Basic)
    Walkthrough: Persisting an Object (C# and Visual Basic)
    Data Templating Overview
    INotifyPropertyChanged Interface