views:

128

answers:

2

I am trying to create a custom Output Window for my software that is bound to my ViewModel. Usually in the past I have always used a TextBox and used the appendText() method to write to the Output Window. Of course with a ViewModel and trying to bind to a Textbox, it seems you can only Bind to the Text property. Below is what I am trying to do:

XAML:

<TextBox Text="{Binding Output}"></TextBox>

C#

public class ViewModel : DependencyObject
    {
        public static readonly DependencyProperty OutputProperty = DependencyProperty.Register("Output", typeof(ObservableCollection<string>), typeof(MVVMTestViewModel), new UIPropertyMetadata(null));

        public ObservableCollection<string> Output
        {
            get
            {
                return (ObservableCollection<string>)GetValue(OutputProperty);
            }
            set
            {
                SetValue(OutputProperty, value);
            }
        }

        public ViewModel()
        {
            Output = new ObservableCollection<string>();
        }

        public void OutputMessage(string message)
        {
            Output.Add(message);
        }
    }

Of course this is not possible because Text cannot be assigned to an Observable Collection.

Note: I could use a ListView, or a ListBox and use an observable collection. But I do not like the selection mode. I like to be able to highlight the text of a TextBox so that I can copy and paste it out of the Window. The isReadOnly property allows me to do this fairly easily.

Is there an easy way to do this? Another Control I haven't though of perhaps?

+1  A: 

You could use a converter to convert the collection to a string :

public class ListToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        IEnumerable lst = value as IEnumerable;
        if (lst != null)
        {
            StringBuilder sb = new StringBuilder();
            foreach(var item in lst)
            {
                sb.AppendLine(item.ToString());
            }
            return sb.ToString();
        }
        return null;
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }

}
Thomas Levesque
My output is sometimes in the thousands of lines(rarely).. But when it is, would this not re-output the entire ObservableCollection each time I added a new string?
jsmith
Yes... That's an issue indeed. I don't think you can handle that easily through binding. You should probably implement event handlers for the collection instead
Thomas Levesque
Can you give me a good link or even an example on how to do this? I'm not real familiar with event handlers mixed with binding..
jsmith
I'm not saying that you should "mix" events and bindings... I was suggesting that you use only events. Just append a line to the TextBox when a line is added to the collection (CollectionChanged event)
Thomas Levesque
+1  A: 

I think what Thomas was saying is that you could have the following in your Window's codebehind:

public partial class MyWindow : Window
{
    ViewModel _vm;

    public MyWindow(ViewModel vm)
    {
        InitializeComponent();
        _vm = vm;
        _vm.Output.CollectionChanged += Output_CollectionChanged;
    }

    void Output_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        foreach(object item in e.NewItems)
        {
            outputTextBox.AppendText(item.ToString());
        }
    }
}

This way, the window class will automatically append any text that is added to the Output collection.

Another way to do it (though it may not be considered pure MVVM) is by using an interface as follows:

public interface IAppendOutput
{
    void Append(string text);
}

public partial class MyWindow : Window, IAppendOutput
{
    public MyWindow()
    {
        InitializeComponent();
    }

    public void Append(string text)
    {
        outputTextBox.AppendText(text);
    }
}

public class ViewModel
{
    IAppendOutput _outputter;
    public ViewModel(IAppendOutput outputter)
    {
        _outputter = outputter;
    }

    public void AppendOutput(string text)
    {
        _outputter.Append(text);
    }
}

Then in your main app:

...
MyWindow window = new MyWindow();
ViewModel vm = new ViewModel(window);
window.DataContext = vm;
window.Show();
vm.AppendOutput("This is a test.");
...

This way may not be the best way of doing it, but it may be a useful method for writing quick duct-tape code for other circumstances. It is still unit-testable because the ViewModel has no knowledge of the window itself, only an interface. It is still flexible because you can customize any sort of object so that it implements the IAppendOutput interface.

Phil