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

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.

Related

ItemControl DataTemplate with UIElement list

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.

How do i create a usercontrol that i can place stuff into?

I'm trying to make a custom ScrollViewer uc and it occurred to me that I wouldn't know how to put things within the tags of it. For an example
<CustomScrollViewer>
<This is the place where i want to put things>
</CustomScrollViewer>
Is it possible to define an area where the "inside" things will be put?
You may doing this like as: create a dependency property for UserControl of IEnumerable type, and bind the ItemsSource, that you want scrolling.
MainWindow
<Window x:Class="ScrollViewerUserControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:this="clr-namespace:ScrollViewerUserControl"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<x:Array x:Key="ParametersArray" Type="{x:Type sys:String}">
<sys:String>0</sys:String>
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</x:Array>
</Window.Resources>
<Grid>
<this:CustomScrollViewer Width="100"
Height="30"
ItemsSource="{StaticResource ParametersArray}" />
</Grid>
</Window>
CustomScrollViewer.xaml
<Grid>
<ScrollViewer Background="Aquamarine"
Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}">
<ItemsControl ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
</ScrollViewer>
</Grid>
CustomScrollViewer.xaml.cs
public partial class CustomScrollViewer : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustomScrollViewer));
public IEnumerable ItemsSource
{
get
{
return this.GetValue(ItemsSourceProperty) as string;
}
set
{
this.SetValue(ItemsSourceProperty, value);
}
}
public CustomScrollViewer()
{
InitializeComponent();
}
}
But I think, in this case it's better create a CustomScrollViewer like this:
public class CustomScrollViewer : ScrollViewer
{
// Your additional logic here
}
And in XAML use like this:
<this:CustomScrollViewer Width="100" Height="20">
<ItemsControl ItemsSource="{StaticResource ParametersArray}" />
</this:CustomScrollViewer>
What do you mean 'put things into'?
In your example of a custom ScrollViewer, it would work exactly how you'd use a normal ScrollViewer, for example:
<ScrollViewer>
<DataGrid /> // or whatever controls you want to place within the scrollviewer
</ScrollViewer>
becomes
<CustomScrollViewer>
<DataGrid /> // or whatever controls you want to place within the scrollviewer
</CustomScrollViewer>
For this kind of layout control, it wraps other controls ... so just extend the ScrollViewer to add whatever changes you want to it.
If you mean having new properties on that CustomScrollViewer then follow Anatoliy's guidelines on creating new dependecy properties, which would allow you to do things like ...
<CustomScrollViewer myCustomProperty="WickeyWickeyWhack">
<DataGrid /> // or whatever controls you want to place within the scrollviewer
</CustomScrollViewer>

MouseDragElementBehavior in DataTemplate

So here's an issue that I'm having. I'm trying to use MouseDragElementBehavior in listbox. I was able to make it work when I was creating items in listbox directly, as in this example:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
<Border Width="20" Height="20">
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
<Rectangle Fill="Red"/>
</Border>
</ItemsControl.Items>
</ItemsControl>
But as soon as I've started using DataTemplate, it stopped working.
<ItemsControl Grid.Column="1" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Items>
Test item
</ItemsControl.Items>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="20" Height="20">
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
<Rectangle Fill="Red"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Any ideas as to why? I can't really figure out how a DataTemplate would affect MouseDragElementBehavior.
The MouseDragElementBehavior acts upon the FrameworkElement you attach it to. In your case it is the Border element which will be contained by a ContentPresenter which is the container generated by the ItemsControl. You have set ConstrainToParentBounds="True" which will ensure the visual will not be displayed outside its container, in this case the ContentPresenter. There are a few options, some easy, one probably not worth undertaking (but I did to figure some stuff out).
Set ConstrainToParentBounds="False". I am supposing that you don't want the Border to leave the ItemsControl so this probably won't suit.
Set the ItemContainerStyle to a Style which sets the Template to and adds the interaction to a similarly configured ContentPresenter. The base implementation of the ItemsControl uses a vanilla ContentPresenter. A caveat here is that if you aren't using UI elements as items you will need to wrap the item in one using a custom items control (see this answer on setting the container style):
<ItemsControl Grid.Column="1" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<ContentPresenter>
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style >
</ItemsControl.ItemContainerStyle>
<ItemsControl.Items>
Test item
</ItemsControl.Items>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="20" Height="20">
<Rectangle Fill="Red"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Attach the interaction using the ItemsControl.ItemContainerStyle. This is a little involved because the Interaction.Behaviors attached property only has a setter:
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="beh:AddCollectionsToSetter.Behaviors">
<Setter.Value>
<beh:BehaviorCollection>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</beh:BehaviorCollection>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
For this I had to create a separate attached property AddCollectionsToSetter.Behaviors which is read/write and a BehaviorCollection that allows the interactions to be added to.
public static class AddCollectionsToSetter
{
#region Behaviors Dependency Property (Attached)
/// <summary>Gets the behaviours to add.</summary>
public static BehaviorCollection GetBehaviors(DependencyObject obj)
{
return (BehaviorCollection)obj.GetValue(BehaviorsProperty);
}
/// <summary>Sets the behaviours to add.</summary>
public static void SetBehaviors(DependencyObject obj, BehaviorCollection value)
{
obj.SetValue(AddCollectionsToSetter.BehaviorsProperty, value);
}
/// <summary>DependencyProperty backing store for <see cref="Behaviors"/>. Represents the behaviours to add.</summary>
/// <remarks></remarks>
public static readonly DependencyProperty BehaviorsProperty =
DependencyProperty.RegisterAttached("Behaviors", typeof(BehaviorCollection), typeof(AddCollectionsToSetter), new PropertyMetadata(null, BehaviorsPropertyChanged));
private static void BehaviorsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var oldBehaviors = (BehaviorCollection)e.OldValue;
var newBehaviors = (BehaviorCollection)e.NewValue;
var interaction = Interaction.GetBehaviors(d);
interaction.RemoveRange(oldBehaviors); // extension method, simple iterate and remove
interaction.AddRange(newBehaviors.Clone()); // extension method, simple iterate and add
}
#endregion Behaviors Dependency Property (Attached)
}
public class BehaviorCollection : FreezableCollection<System.Windows.Interactivity.Behavior>
{
public BehaviorCollection()
: base()
{
}
public BehaviorCollection(int capacity)
: base(capacity)
{
}
public BehaviorCollection(IEnumerable<System.Windows.Interactivity.Behavior> behaviors)
: base(behaviors)
{
}
}

How to loop through ItemsControl in WPF?

How can I loop through this ItemsControl and change it's TextBlock background in this Xaml's code behind page on some mouse event. I am new to WPF.
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Name="Data" Text="{Binding }" Width="100" HorizontalAlignment="Left" PreviewMouseLeftButtonDown="MouseLeftButtonDown_Handler"
MouseEnter="MouseEnter_Handler" MouseLeave="MouseLeave_Handler">
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks in advance!!
Actually my requirement is to change individual TextBlock's background color on different mouse events. So i need to get access of TextBlock in code behind and depending upon login I can change that Textblock's background color accordingly. So i think need to iterate ItemsControl. in case if I bind Background Property then all on property change would have effect on all the Textblocks in that ItemsControl. I don't want it in this way. I want to set and change every individual textblock's color differently.
I have access to single one in the eventhandlers that caused that event, but I want to access all the textblocks that are in itemscontrol and change their color acoording to some logic
Solution with background binding like axelle suggested:
You can iterate through the items in the HeaderList and set the background-property.
The Header class must implement the INotifyPropertyChanged Interface
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Path=HeaderList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Background="{Binding Background}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public partial class MainWindow : Window
{
public class Header : NotificationObject
{
public string Text { get; set; }
public Brush Background { get; set; }
}
public IList<Header> HeaderList { get; set; }
public MainWindow()
{
HeaderList = new List<Header>
{
new Header {Text = "header1", Background = Brushes.Red},
new Header {Text = "header2", Background = Brushes.Blue},
new Header {Text = "header3", Background = Brushes.Chartreuse},
};
DataContext = this;
InitializeComponent();
}
}
If I understand your question correctly, you'd want to bind the TextBlock background to a value in your datacontext, and change that value on your mouse event.
don't loop through the itemscontrol, better use a Trigger to apply the changes to your textblock :)
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>

Binding for TabItem's content controls

I have a TabControl with ItemsSource set to ObservableCollection<BookTab> and using ContentTemplateSelector to create different tabs.
class BookTab
{
public string Name { get; set; }
public string Type { get; set; }
public object Data { get; set; }
}
<TabControl Name="tabControl"
ContentTemplateSelector="{StaticResource tabTemplateSelector}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Type in BookTab determines DataTemplate used in the appropriate tab, Name is displayed on the tab header, and Data supposed to be displayed in tab's content, i.e. DataGrid.
Data is set to ObservableCollections of different types.
DataTemplate may look like this:
<DataTemplate x:Key="bookTabTemplate">
<TabItem Name="bookTab">
<Grid>
<DataGrid Name="bookGrid">
...
</DataGrid>
</Grid>
</TabItem>
</DataTemplate>
I tried different ways to bind Data property to DataGrid's ItemsSource, but all I got is grid displaying word "Book" (BookTab's Name property value).
My guess is I have to somehow propagate TabControl's binding down to DataGrid, but I cannot figure it out.
I would do it like this:
<TabControl SelectedItem="{Binding CurrentBook}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding BookList}">
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<ContentControl Content="{Binding Data}"
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
... and later you define in your app.xaml how the content of your data is presented...
<DataTemplate DataType="{x:Type viewmodel:bookviewmodel1}">
<view:bookview1/>
</DataTemplate>
All you have to do, is creating a view (usercontrol) for each type.
HTH
your datagrid data context is probably the BookTab (you can confirm this using snoop )
If this is correct, all you have to do is bind the datagrid itemssource to the BookTab Data property
<DataGrid Name="bookGrid" ItemsSource="{Binding Path=Data}" />
This should do the trick

Resources