Question

After 20+ years of windows programming and two days of WPF I feel I know nothing :-)

My first WPF program is really simple: You drop a few files from explorer and their names are shown in a TextBox control. (It works fine for a ListBox, but that isn't what I want. And of course adding the lines manually in the Drop event works as well - but I want to learn about the Binding ways..)

So I wrote a Converter but somehow it insn't used (breakpoints won't get hit) and nothing shows up.

It should be a small thing or maybe I'm totally off track. Found many examples for similar things from which i patched this together but still can't get it to work.

(I probably won't need the ConvertBack, but wrote it down anyway..)

Here is the converter class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace WpTest02
{
  public class ListToTextConverter : IValueConverter
  {
      public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string s in (List<string>)value) sb.AppendLine(s);
            return sb.ToString();
        }

      public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string[] lines = ((string)value).Split(new string[] { @"\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            return lines.ToList<String>();
        }
   }

}

The MainWindow.xaml, where I suspect the binding problem to be:

<Window x:Class="WpTest02.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpTest02"
        Title="MainWindow" Height="350" Width="525"
        >
    <Window.Resources>
        <local:ListToTextConverter x:Key="converter1" />
    </Window.Resources>

    <Grid >
        <TextBox Name="tb_files" Margin="50,20,0,0"  AllowDrop="True" 
                 PreviewDragOver="tb_files_PreviewDragOver" Drop="tb_files_Drop" 
                 Text="{Binding Path=fileNames, Converter={StaticResource converter1} }"
                 />
    </Grid>
</Window>

And the Codebehind with nothing more the the data property to bind to and the drag&drop code, which works.

using System; 
//etc ..

namespace WpTest02
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            fileNames = new List<string>();
        }


        public List<string> fileNames { get; set; }

        private void tb_files_Drop(object sender, DragEventArgs e)
        {
            var files = ((DataObject)e.Data).GetFileDropList();
            foreach (string s in files) fileNames.Add(s);

            // EDIT: this doesn't help ? Wrong!                     
            // EDIT: this is actually necessary! :                     
            tb_files.GetBindingExpression(TextBox.TextProperty).UpdateTarget();

            // this obviosly would work:
            //foreach (string s in files) tb_files.Text += s + "\r\n";
        }

        private void tb_files_PreviewDragOver(object sender, DragEventArgs e)
        {
            e.Handled = true;
        }

    }
}

Note: I have editied the last piece of code to stress the importance of the UpdateTarget call.

Was it helpful?

Solution 2

The DataContext of the TextBox must be set to bind the data. Like this:

    public MainWindow()
    {
        InitializeComponent();
        fileNames = new List<string>();
        this.tb_files.DataContext = this;
    }

OTHER TIPS

For Binding to work you need to assign Window's DataContext to the instance where property resides which in your case is Window class itself.

So set DataContext in constructor and it should work fine:

public MainWindow()
{
    InitializeComponent();
    fileNames = new List<string>();
    DataContext = this;
}

OR

You have to explicitly resolve the binding from XAML using ElementName in your binding:

<Window x:Name="myWindow">
  ....
  <TextBox Text="{Binding Path=fileNames, ElementName=myWindow,
                          Converter={StaticResource converter1}}"/>

For XAML approach to work, you have to initialize the list before XAML gets loaded i.e. before InitializeComponent gets called.

fileNames = new List<string>();
InitializeComponent();

this is the general pattern which should work for you. Feel free to contact me with any questions. Best of luck! ~Justin

<Window xmlns:vm="clr-namespace:YourProject.YourViewModelNamespace"
        xmlns:vc="clr-namespace:YourProject.YourValueConverterNamespace">
    <Window.Resources>
        <vc:YourValueConverter x:key="YourValueConverter" />
    </Window.Resources>
    <Window.DataContext>
        <vm:YourViewViewModel />
    </Window.DataContext>
    <TextBox Text="{Binding MyItems, Converter={StaticResource YourValueConverter}}"/>
</Window>

public class YourViewViewModel : ViewModelBase
{
    ObservableCollection<string> _myItems;
    ObservableCollection<string> MyItems
    {
        get { return _gameProfileListItems; }
        set { _gameProfileListItems = value; OnPropertyChanged("MyItems"); }
    }
    
    public void SetMyItems()
    {
        //    go and get your data here, transfer it to an observable collection
        //    and then assign it to this.GameProfileListItems (i would recommend writing a .ToObservableCollection() extension method for IEnumerable)
        this.MyItems = SomeManagerOrUtil.GetYourData().ToObservableCollection();
    }
}

public class YourView : Window
{
    YourViewViewModel ViewModel
    {
        { get return this.DataContext as YourViewViewModel; }
    }

    public void YourView()
    {
        InitializeComponent();

        InitializeViewModel();
    }

    void InitializeViewModel()
    {
        this.ViewModel.SetMyItems();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top