Populating a Wrappannel Dynamically in MVVM
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");
开发者_Go百科 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!
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);
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.
精彩评论