Richard Gorrin
Richard Gorrin

Reputation: 43

ViewModel's exposed data depends on size of the View

Background

I am a beginner WPF developer. As a learning exercise I am attempting to recreate Visual Studio's Memory window using WPF with the MVVM pattern. For those of you unfamiliar with this window.

enter image description here

Implementation Outline

For simplicity we assume the memory is readonly and will not change. As per the MVVM pattern we define the following entities:

Model

The Model is a simple object that contains 2 properties:

  1. long Address
    • Address of where the Data resides in memory
  2. byte[] Data

The Model represents a sequence of bytes (Data) at a defined memory address (Address).

ViewModel

The ViewModel takes the Model as input and exposes the following properties:

  1. string Addresses

    • A string representing the addresses of the bytes displayed on the same row. Referencing the image in the Background section, the Addresses string contains the contents of the first column, that is:

      string Addresses = "0x022699B0" + '\n' + "0x022699C8" + '\n' + "0x022699E0" + '\n' + ...

  2. string HexBytes

    • A hexadecimal representation of the Data in the Model. Referencing the image in the Background section, the HexBytes string contains the contents of the second column, that is:

      string HexBytes = "c0 ac 45 68 06 ..."

  3. string ASCIIBytes

    • An ASCII representation of the Data in the Model. Referencing the image in the Background section, the ASCIIBytes string contains the contents of the third column, that is:

      string ASCIIBytes = "A.Ch....9.1.1.1.2.5 ..."

To summarize, the ViewModel exposes the data given my the Model in a View friendly format.

View

The View is probably easiest to describe via an XAML code snippet:

<UserControl x:Class="HexEditor.HexView"
         xmlns  ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:z="clr-namespace:HexEditor">

<UserControl.DataContext>
    <z:MemoryViewModel/>
</UserControl.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="3*"/>
        <ColumnDefinition Width="1*"/>
    </Grid.ColumnDefinitions>

    <TextBox Grid.Column="0" IsReadOnly="True" Cursor="Arrow" FontFamily="Consolas" Text="{Binding Addresses, Mode=OneWay}"/>
    <TextBox Grid.Column="1" IsReadOnly="True" TextWrapping="Wrap" Cursor="Arrow" FontFamily="Consolas" Text="{Binding HexBytes, Mode=OneWay}" />
    <TextBox Grid.Column="2" IsReadOnly="True" TextWrapping="Wrap" Cursor="Arrow" FontFamily="Consolas" Text="{Binding ASCIIBytes, Mode=OneWay}" />
</Grid>

Notice the TextWrapping attribute has been set on the second and third columns. This allows us to display a variable number of bytes per row, depending on the size of the Window.

Problem

The problem I am encountering is that the properties the ViewModel must expose depend on the size of the View. In the MVVM pattern, the ViewModel should be independent of the View. This is not a problem for HexBytes and ASCIIBytes as these are generated from the Model. The problem is how to generate the Addresses? To generate this string I must know the number of characters that will be displayed on each line (so that I can calculate the correct address of the first byte of the next line). This is unfortunately something only the View knows about, as it depends on the size of the View.

Proposed Solution

My attempt to solve this problem is to capture the SizeChanged events in the View code-behind and use the TextBox.GetLineLength method to inform the ViewModel of how many bytes can fit on a line. The ViewModel can then consume this value to generate the correct Addresses property.

Discussion

I have two questions regarding my proposed solution:

  1. Is there a more elegant way of informing the ViewModel on the number of characters that can be displayed on 1 line of the second TextBox?
  2. Am I using the correct Controls and the correct approach more importantly to get to my end result (i.e. recreating the Memory Window of Visual Studio)?

Thanks so much for any input or comments.

Upvotes: 4

Views: 738

Answers (2)

McGarnagle
McGarnagle

Reputation: 102783

Is there a more elegant way of informing the ViewModel on the number of characters that can be displayed on 1 line

I think your approach is the best one. The outcome you have in mind -- variable column width with inferred column headers -- implies a dependency of the view-model on the view. (Some MVVM approaches use some kind of "messaging" framework (eg, event aggregators) to communicate between view and view-model, but that is just a more formalized way of having the view directly invoke a method on the view-model.) Also, you will need to be handling the SizeChanged event in any case, in order to re-generate the column headers on each re-size.

Am I using the correct Controls and the correct approach more importantly to get to my end result

It seems fine. You might consider using an ObservableCollection<string> along with an ItemsControl or ListBox (instead of the flat string with line breaks). That would allow you more control over the UI, enabling things like highlighting/selection of individual addresses; synchronized scrolling of the addresses/bytes; and virtualized panels to handle larger data sets without the UI grinding down.

Upvotes: 0

Dean Kuga
Dean Kuga

Reputation: 12119

When you capture that value in your event handler just update the VM property that keeps this value from within that event handler and that way VM will always have that value as soon as the event fires...

Since your VM is already the DataContext of your view this is not breaking the MVVM pattern and updating that value of the VM property is very simple:

This is all in your View code-behind:

Field:

private MyViewModel vm;

In view constructor (or DataContextChanged event if you assign DataContext dynamically):

vm = DataContext as MyViewModel; //Get VM instance from view's DataContext

In event handler:

vm.AddressLenght = TextBox.GetLineLength;

Upvotes: 1

Related Questions