ItemControl DataTemplate with UIElement list - wpf

I'm trying to develop library of user controls that arranges multiple UIElements in specific way. I use ItemControl to show list of UIElements. I want to surround every item from item control with Stack.
I would like to use my library more or less this manner.
<pcLayouts:ListLayout>
<pcLayouts:ListLayout.ParentItems>
<TextBlock Width="145">1</TextBlock>
<TextBlock>2</TextBlock>
<TextBlock>3</TextBlock>
</pcLayouts:ListLayout.ParentItems>
</pcLayouts:ListLayout>
I declared dependency property in backing class ListLayout cs and xaml files.
public static readonly DependencyProperty ParentItemsProperty = DependencyProperty.Register(
"ParentItems", typeof(ObservableCollection<UIElement>), typeof(ColumnLayout),
new PropertyMetadata(new ObservableCollection<UIElement>()));
...
public ObservableCollection<UIElement> ParentItems
{
get { return (ObservableCollection<UIElement>)GetValue(ParentItemsProperty); }
set
{
SetValue(ParentItemsProperty, value);
OnPropertyChanged();
}
}
<StackPanel x:Name="MainPanel" Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ParentItems, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<WHAT SHOULD I PUT HERE??/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
It seems DataTemplate isn't used at all when binding to Binding ParentItems, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}. How can I use this data template or is there another way?

this is because ItemsControl.IsItemItsOwnContainerOverride returns true for UIElement. Normally a ContentPresenter is used which generates the DataTemplate.
If you insist on using DataTemplate you create a new class derived from ItemsControl and override IsItemItsOwnContainerOverride to return false.

Related

How to Merge user controls that Binds to SAME obj smoothly?

I'm trying to merge some user controls that are binded to same target. At the start, it looks simple but I have no idea with this how can I deliver binding target to daughter control (controls inside merge control)?
I want to make this:
<Canvas>
<local:Teeth x:Name="sideR" Points="{Binding Points[0]}" IsClosedCurve="{Binding IsClosedCurve}"/>
<local:WrapTeeth Points="{Binding Points[0]}"/>
<ListBox ItemsSource="{Binding Points[0]}" ItemContainerStyle="{StaticResource PointListBoxItemStyle}">
<ListBox.Template>
<ControlTemplate>
<Canvas IsItemsHost="True"/>
</ControlTemplate>
</ListBox.Template>
</ListBox>
</Canvas>
into
<local:MergeControl Points="{Binding Points[0]}"/>
Your UserControl should have a Points dependency property like shown below. It is not clear from your question whether you need a more specialized collection type than IEnumerable. Possibly replace it with PointCollection or something more suitable.
public partial class MergeControl : UserControl
{
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register(
"Points", typeof(IEnumerable), typeof(MergeControl));
public IEnumerable Points
{
get { return (IEnumerable)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
public MergeControl()
{
InitializeComponent();
}
}
The elements in the UserControl's XAML would bind to this property by RelativeSource Bindings. You may need to define another property for the IsClosedCurve Binding of the Teeth element.
<UserControl ...>
<UserControl.Resources>
<Style x:Key="PointListBoxItemStyle" TargetType="ListBoxItem">
...
</Style>
</UserControl.Resources>
<Canvas>
<local:Teeth x:Name="sideR"
Points="{Binding Points, RelativeSource={RelativeSource AncestorType=UserControl}}"
IsClosedCurve="{Binding IsClosedCurve, ...}"/>
<local:WrapTeeth
Points="{Binding Points, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<ListBox
ItemsSource="{Binding Points, RelativeSource={RelativeSource AncestorType=UserControl}}"
ItemContainerStyle="{StaticResource PointListBoxItemStyle}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Canvas>
</UserControl>
Note also that ItemsControls have an ItemsPanel property to set the Panel element that is used to contain their items.

Bind Usercontrol contained in a ObservableCollection<CustomClass> in a WrapPanel

I have a class defined this way:
public class CustomClass
{
string Name;
int Age;
Usercontrol usercontrol;
}
where Usercontrol is a visual element that I want to insert in a WrapPanel.
CustomClass is organized in a static ObservableCollection.
public static class CollectionClass
{
public static ObservableCollection<CustomClass> MyCollection = new ObservableCollection<CustomClass>();
}
I am looking to bind the usercontrol property of the CustomClass in the collection to be visualized in the WrapPanel, so I have the visual elements showed in the same order as the elements in the collection.
Right now I am populating the WrapPanel manually by code, but I figured that there has to be a way to do it quickly and easily through databinding.
I am trying to do it with a ItemsControl defined this way:
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
but I don't know how to make the magic happen.
EDIT:
I tried the solution proposed by ChrisO, implemented like that:
1 - I made the collection a property
2 - I made the UserControl a property
3 - I set DataContex from code:
SensorMenuWrap.Datacontext = CollectionClass.MyCollection
4 - The binding:
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding usercontrol}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now I can visualize the first element of the collection. If the first element changes, I visualize the new first element. How can I visualize the entire collection?
I haven't tested this. Also, make sure that userControl is a property rather than a field. I'm assuming you know how to set the DataContext up correctly to refer to the MyCollection property.
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding userControl}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You should also consider referring directly to the UserControl in the DataTemplate rather than binding to it as a property on the class. That would look like this
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
In response to your edit, you would be much better tackling this with a reusable UserControl with DataBindings on the properties of CustomClass. Here's what I have to achieve that:
CollectionClass
public static class CollectionClass
{
public static ObservableCollection<CustomClass> MyCollection { get; set; }
static CollectionClass()
{
MyCollection = new ObservableCollection<CustomClass>();
MyCollection.Add(new CustomClass { Age = 25, Name = "Hamma"});
MyCollection.Add(new CustomClass { Age = 32, Name = "ChrisO"});
}
}
CustomClass
public class CustomClass
{
public string Name { get; set; }
public int Age { get; set; }
}
UserControl contents
<StackPanel>
<TextBlock Background="Tan" Text="{Binding Name}" />
<TextBlock Background="Tan" Text="{Binding Age}" />
</StackPanel>
MainWindow
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<so25728310:Usercontrol Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here, instead of having the Usercontrol baked into your data, you're keeping it in the view. It's always good to have a separation between your view and data. You could even do away with the Usercontrol and have the two TextBoxes directly in the DataTemplate but it depends on whether you would want to reuse that view elsewhere.

Using different panels for different sizes of items in WPF

I want to use different panels for my container's item count. Is there a way that I can achieve this?
For example, I want to use PanelA for less than 5 items, PanelB for more.
I implemented the DataTemplateSelector class for this purpose and it seems to work. But when it comes to draw the UI element, I get no results but the blank white screen.
public class PanelDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
MyViewModel VM = (VisualTreeHelper.GetParent(container) as ContentControl).DataContext as MyViewModel;
DataTemplate template = null;
if (VM.ItemsList.Count < 5)
template = element.FindResource("PanelA") as DataTemplate;
else
template = element.FindResource("PanelB") as DataTemplate;
return template;
}
}
Following is the one of the templates in my XAML code. Second one is just the same but PanelB.
<DataTemplate x:Key="PanelA">
<ListBox x:Name="ItemsListBox" ItemsSource="{Binding Path=ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=Image}"/>
<TextBlock Text="{Binding Path=Label}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<mynamespace:PanelA />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</DataTemplate>
<DataTemplate x:Key="PanelB">
...
</DataTemplate>
After resources, I have the following ContentControl section.
<Grid>
<ContentControl>
<ContentControl.ContentTemplateSelector>
<local:PanelDataTemplateSelector />
</ContentControl.ContentTemplateSelector>
</ContentControl>
</Grid>
Thanks

Setting DataContext for binding RelayCommand in xaml

In the following xaml code, I'm trying bind a RelayCommand ResourceButtonClick, which is in the view model. In addition to that, I want to pass the Resource.Id as a parameter to this command.
However, ResourceButtonClick is not called. I suspect that by setting the ItemsSource to Resources, I override the data context, which was view model.
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Command="{Binding ResourceButtonClick}"
CommandParameter="{Binding Id}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Here's the RelayCommand in the view model.
public RelayCommand<int> ResourceButtonClick { get; private set; }
The constructor of the view model:
public ResourcesViewModel()
{
this.ResourceButtonClick =
new RelayCommand<int>((e) => this.OnResourceButtonClick(e));
}
The method in the view model:
private void OnResourceButtonClick(int suggestionId)
{
...
}
I have two questions: First, how can I call the ResourceButtonClick command. Second, how can I pass Resource.Id as a parameter to that command.
Any suggestion will be appreciated.
Maybe you can show your complete ViewModel class? Assuming, that the ResourceButtonClick command is on the ViewModel which also holds the collection Resources, you're trying to access the command on the wrong object (on the item in Resources, instead of the ViewModel which holds the Resources collection and the command).
Therefore you would have to access the command on the 'other' DataContext, this is the DataContext of the ItemsControl not of it's item. The easiest way is to use the ElementName attribute of the binding:
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}" Name="ResourcesItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Command="{Binding DataContext.ResourceButtonClick, ElementName=ResourcesItemsControl}"
CommandParameter="{Binding Id}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Maybe this solves the problem, otherwise please let me know and provide some more detail.
I usually pass the whole item as a CommandParameter, not an ID. This doesn't cost anything and you don't have to translate the ID back to the item. But that depends on your case.
Hope this helps.
I resolved this in a such way:
In View.xaml
1) I added a property SelectedItem for my ListView:
<ListView Name="MyList" ItemsSource="{Binding MyList}" SelectedItem="{Binding MySelectedItem, Mode=TwoWay}" >
2) I added a Command property to a button:
In viewModel:
3) I added a command handler:
MyCommand = new RelayCommand(MyMethod);
4) I added method MyMethod which takes value(s) from MySelectedItem property:
private void MyMethod ()
{
MyType mt = MySelectedItem;
//now you have access to all properties of your item via mt object
}

wpf Usercontrol template

In my MVVM app I have a treeview representing records in a database. My views and viewmodels are linked in a resource dictionary like this
<DataTemplate DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
I want to display a preview of the view when a user hovers over an icon using the tooltip. My HierarchicalDataTemplate in the treeview is this
<HierarchicalDataTemplate DataType="{x:Type vm:TrialSiteViewModel}"
ItemsSource="{Binding Path=Children}">
...
<Button Style="{StaticResource previewButtonStyle}">
<Button.ToolTip>
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Button.ToolTip>
</Button>
</StackPanel>
</HierarchicalDataTemplate>
This correctly picks up the TrialSiteViewModel that is the DataContext for the Treeviewitem.
ObjectPreview uses a viewbox and contentcontrol to display the view of the record
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}">
</ContentControl>
</Viewbox>
and the code behind contains the dependency property
public partial class ObjectPreview : UserControl
{
public ObjectPreview()
{
InitializeComponent();
}
public static readonly DependencyProperty _previewObjectProperty =
DependencyProperty.Register("PreviewObject", typeof(TreeViewItemViewModel), typeof(ObjectPreview));
public TreeViewItemViewModel PreviewObject
{
get { return (TreeViewItemViewModel)GetValue(_previewObjectProperty); }
set { SetValue(_previewObjectProperty, value); }
}
}
The problem I'm having is that the Template used to display the object is the same as that used in the treeview. This simply shows an icon and an object summary (ie. Primary Key and one or two key fields) rather than the entire template as defined in the view TrialSiteView. If I amend the code to use a button Command on the TrialSiteViewModel and inject it into ObjectPreview I can set the contentcontrol in the code behind and the TrialSiteView is used.
I'm guessing that somehow the Template is inferred from the TreeViewItem. Can anyone tell me how I can ensure the tooltip uses the TrialSiteView?
UPDATE
Ok, so I've fixed this but had to resort to code behind and removed the usercontrol and put the view directly in the tooltip. The key bit is getting the datatemplate from the resources. I'd tried to do this previously by assigning a key to the datatemplate, but either my code was flawed or it did not work. Anyhow, this works but is not the preferred Xaml solution.
private void PreviewObject_MouseEnter(object sender, MouseEventArgs e)
{
Image image = (Image)sender;
var key = new System.Windows.DataTemplateKey(image.DataContext.GetType());
var datatemplate = (DataTemplate)this.FindResource(key);
ToolTip tooltip = new ToolTip();
tooltip.Style = VisualUtils.GetResource<Style>("ControlTemplates.xaml", "toolTipWithContentStyle");
tooltip.MaxWidth = 460;
ContentControl contentcontrol = new ContentControl();
contentcontrol.ContentTemplate = datatemplate;
contentcontrol.Content = image.DataContext as TreeViewItemViewModel;
Viewbox viewbox = new Viewbox();
viewbox.Stretch = Stretch.Uniform;
viewbox.Child = contentcontrol;
tooltip.Content = viewbox;
image.ToolTip = tooltip;
}
What you need to do is to specify explicitly what data template to use. In order to do that just add a template property along with the PreviewObject property in the preview control:
public static readonly DependencyProperty _previewObjectTemplateProperty =
DependencyProperty.Register("PreviewObjectTemplate", typeof(DataTemplate), typeof(ObjectPreview));
public DataTemplate PreviewObjectTemplate
{
get { return (DataTemplate)GetValue(_previewObjectTemplateProperty); }
set { SetValue(_previewObjectTemplateProperty, value); }
}
Then, in the ObjectPreview.xaml add the ContentTemplate property that is bound to the PreviewObjectTemplate property:
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}"
ContentTemplate="{Binding PreviewObjectTemplate}" >
</ContentControl>
</Viewbox>
And finally, give a key to your data template and specify a reference to it explicitly when you declare ObjectPreview:
<DataTemplate x:Key="FullViewTemplate" DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
...
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
PreviewObjectTemplate="{StaticResource FullViewTemplate}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>

Resources