1

this is regarding a best practice advice regarding using MVVM.

I am in need to populate a wrappanel as and when elements are dropped in to it. The elements are not uniform, they can be either labels or text boxes. Depending on a value of a parameter the element added varies.

I did this in code behind. Now I am in the process of moving the entire thing to an MVVM model am stuck with the way to do this without affecting the MVVM core principals. In this code I have both UI elements and logic content to gether which are tightly related; and I have become incapable to seperate the two to suite MVVM.

I tried creating the UI elements in the VM, populating a ObservableCollection of type UIElement and binding it to the itemssource property (Subsequently I changed wrappanel to be a listview to be effective in the whole thing). But this did not work out since when I bind the elements there is no way the code can understand which UIelement.

Posted below is the section of the code which I need to seperate:

private void CreateVisulaQueryContent() {

            VisualQueryObject visualQueryData = new VisualQueryObject();

            VisualQueryObject helperVisualQueryObject = DraggedData as    VisualQueryObject;


            //***Taking a copy of the static DraggedData object to be bound

                visualQueryData.ColumnDiscriptor = helperVisualQueryObject.ColumnDiscriptor;

            visualQueryData.ComparedValue = helperVisualQueryObject.ComparedValue;

            visualQueryData.JoinWithColumnDescriptor = helperVisualQueryObject.JoinWithColumnDescriptor;

            visualQueryData.LabelType = helperVisualQueryObject.LabelType;
            visualQueryData.OperatorValue = helperVisualQueryObject.OperatorValue;


            if (visualQueryData.LabelType == "column")
            {

                ColumnDescriptionObject descriptionValue = visualQueryData.ColumnDiscriptor;
                Label droppedElement = new Label();

                Binding binding = new Binding();
                binding.Source = visualQueryData;
                binding.Path = new PropertyPath("ColumnDiscriptor");
                binding.Mode = BindingMode.TwoWay;
                droppedElement.SetBinding(Label.DataContextProperty, binding);

                droppedElement.Content = visualQueryData.ColumnDiscriptor.TableName + "." + visualQueryData.ColumnDiscriptor.ColumnName;


                droppedElement.Foreground = Brushes.White;
                droppedElement.Background = Brushes.DarkOrange;

                droppedElement.BorderThickness = new Thickness(5);

                droppedLabel.MouseDoubleClick += columnLabel_MouseDown;
                ViewUIElements.Add(droppedElement);

            }
            else if (visualQueryData.LabelType == "controller")
            {

                Label droppedElement = new Label();

                Binding binding = new Binding();
                binding.Source = visualQueryData;
                binding.Path = new PropertyPath("OperatorValue");
                binding.Mode = BindingMode.TwoWay;
                droppedElement.SetBinding(Label.DataContextProperty, binding);


                droppedElement.Content = draggedContent.OperatorValue;
                droppedElement.Foreground = Brushes.White;
                droppedElement.Background = Brushes.Crimson;
                droppedElement.BorderThickness = new Thickness(5);

                droppedElement.MouseDoubleClick += columnLabel_MouseDown;

                ViewUIElements.Add(new Label());

            }
            else if (visualQueryData.LabelType == "value")
            {
                TextBox droppedElement = new TextBox();

                Binding binding = new Binding();
                binding.Source = visualQueryData;
                binding.Path = new PropertyPath("ComparedValue");
                binding.Mode = BindingMode.TwoWay;
                droppedElement.SetBinding(TextBox.TextProperty, binding);

               droppedElement.MouseDoubleClick += columnLabel_MouseDown;

                ViewUIElements.Add(droppedElement);
            }

            QueryDesignerModel.QueryDesignHelperCollection.Add(visualQueryData);

    }

Any help is deeply appreciated!

picmate 涅
  • 3,951
  • 5
  • 43
  • 52
  • 1
    What is the question exactly? I know how to display items dynamically (ItemsSource with ItemPanelTemplate) and how to get rid of UIElements in ViewModels (use datatemplateselector). But what should I write in my answer? – vortexwolf Mar 25 '11 at 09:55
  • Hi vorrtex, thanks for the comment. This is about adding dynamic content to a Wrappanel (or a listview) in a wpf application the MVVM way. Also before adding the content to the panel I need to check the user request and upon that the added element can be either a lable or a textbox. This is the problem in a nut shell. Do let me know if my problem is still vague. – picmate 涅 Mar 25 '11 at 10:54
  • Ok, I write an example after some time. – vortexwolf Mar 25 '11 at 11:27

2 Answers2

3

As I promised, I have created an example in which there is no UIElements inside ViewModels.

First of all, I removed lot of code from your method:

public class MainViewModel
{
    public MainViewModel()
    {
        //For demonstration
        this.ViewUIElements = new ObservableCollection<VisualQueryObject>
        {
            new VisualQueryObject{LabelType = "column", ColumnDiscriptor = new DescriptionModel("Table1", "Column2") },
            new VisualQueryObject{LabelType = "controller"},
            new VisualQueryObject{LabelType = "value"},
        };
    }

    public void UpdateCollection(VisualQueryObject helperVisualQueryObject)
    {
        VisualQueryObject visualQueryData = new VisualQueryObject();
        //I would remove copying, but maybe it is intended behavior
        //***Taking a copy of the static DraggedData object to be bound           
        visualQueryData.ColumnDiscriptor = helperVisualQueryObject.ColumnDiscriptor;
        visualQueryData.ComparedValue = helperVisualQueryObject.ComparedValue;
        visualQueryData.JoinWithColumnDescriptor = helperVisualQueryObject.JoinWithColumnDescriptor;
        visualQueryData.LabelType = helperVisualQueryObject.LabelType;
        visualQueryData.OperatorValue = helperVisualQueryObject.OperatorValue;

        this.ViewUIElements.Add(visualQueryData);

        //QueryDesignerModel.QueryDesignHelperCollection.Add(visualQueryData);   //I don't know what this method does
    }

    public ObservableCollection<VisualQueryObject> ViewUIElements { get; private set; }
}

Then I created the DataTemplateSelector class where I've put if-clauses from the function in your question:

public class QueryObjectDateTemplateSelector : DataTemplateSelector
{
    public DataTemplate ColumnTemplate { get; set; }

    public DataTemplate ControllerTemplate { get; set; }

    public DataTemplate ValueTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {
        var visualQueryData = item as VisualQueryObject;
        if (visualQueryData == null)
            return null;

        if (visualQueryData.LabelType == "column")
            return ColumnTemplate;
        else if (visualQueryData.LabelType == "controller")
            return ControllerTemplate;
        else if (visualQueryData.LabelType == "value")
            return ValueTemplate;
        else return null;

    }
}

And that's almost all. Everything else is in xaml:

<Window.Resources>
    <ItemsPanelTemplate x:Key="WrapPanelTemplate">
        <WrapPanel Orientation="Horizontal" />
    </ItemsPanelTemplate>

    <DataTemplate x:Key="ColumnDataTemplate">
        <Label DataContext="{Binding ColumnDiscriptor}" Foreground="White" Background="DarkOrange" BorderThickness="5">
            <TextBlock>
                <Run Text="{Binding TableName}"/><Run Text="."/><Run Text="{Binding ColumnName}"/> 
            </TextBlock>
        </Label>
    </DataTemplate>
    <DataTemplate x:Key="ControllerDataTemplate">
        <Label Content="Controller"/>
    </DataTemplate>
    <DataTemplate x:Key="ValueDataTemplate">
        <TextBox Text="Value"/>
    </DataTemplate>
    <local:QueryObjectDateTemplateSelector x:Key="ModelSelector"
                                           ColumnTemplate="{StaticResource ColumnDataTemplate}"
                                           ControllerTemplate="{StaticResource ControllerDataTemplate}"
                                           ValueTemplate="{StaticResource ValueDataTemplate}"/>
</Window.Resources>
<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

<Grid>
    <ItemsControl ItemsSource="{Binding ViewUIElements}" ItemsPanel="{StaticResource WrapPanelTemplate}" 
                  ItemTemplateSelector="{StaticResource ModelSelector}"/>
</Grid>

I have coded only the Column Template, other templates should be rewritten in xaml too. So now you can call this method:

((MainViewModel)this.DataContext).UpdateCollection(DraggedData as VisualQueryObject);
vortexwolf
  • 13,967
  • 2
  • 54
  • 72
  • Hi Vorrtex, I really really appreciate the effort you have taken to do this code. It is great. I learnt a massive amount of datatemplating from the code you've given. Now I am going to try it. I will comment here about the progress soon. Thanks again. – picmate 涅 Mar 29 '11 at 09:12
1

I'm not quite sure what you are after, but it sounds like you want to use data templating to change the ItemTemplate for a ListBox based on the underlying bound data object type.

So, in your case you can have an ObservableCollection<LabelType> where label type has derived types for each of your label types (column, controller, value), then use implicit data templates (using the DataType property) to select an appropriate template for each sub type.

Alternatively, you could have one LabelType with an enumeration for the type of object.

ObservableCollection<LabelType> Labels ...

Labels.Add(new ControllerLabelType());

...

<ListBox ItemsSource="{Binding Labels}">
  <ListBox.Resources>
    <DataTemplate DataType="{x:Type local:ControllerLabelType}">
      <TextBlock Text="{Binding OperatorValue}" />
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:ValueLabelType}">
      <TextBox Text="{Binding OperatorValue}" />
    </DataTemplate>        
  </ListBox.Resources>
  <ListBox.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel IsItemsHost="True" />
    </ItemsPanelTemplate>
  </ListBox.ItemsPanel>
</ListBox>    

See http://msdn.microsoft.com/en-us/library/ms742521.aspx for more on data templating.

devdigital
  • 34,151
  • 9
  • 98
  • 120