Silverlight ContentControl in ItemTemplate - silverlight

I`m trying to figure out why this code
<UserControl x:Class="TestSilverlight.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<Button x:Key="button" Content="{Binding}" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Yellow">
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{StaticResource button}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
using System.Collections.Generic;
namespace TestSilverlight
{
public partial class MainPage
{
public MainPage()
{
InitializeComponent();
listBox.ItemsSource = new List<string> {"a", "b", "c"}; //without this line it works
}
}
}
doesnt work. It throws Parser Exception (can`t set property Content in ContentControl). Without binding it works perfectly. Is it ok?

It looks like there is something wrong with your logic. If you define a resource of type Button - that makes one instance of a button. You can't have a single instance of a control be a content of multiple controls.
Why not just do this:
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

How to properly bind a shared property of a sub-usercontrol dependency property

I've 4 different collections. Currently I display thoses 4 collections, but I can only have one element selected at the time.
The view where I display the 4 collection:
<UserControl x:Class="xxx.yyy.vvv.Menu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:xxx.yyy.vvv.Menu"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="450">
<Grid Name="RootContainer">
<Grid.DataContext>
<local:MenuViewModel/>
</Grid.DataContext>
<ItemsControl ItemsSource="{Binding Collection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CollectionControl Collection="{Binding}" SelectedElement="{Binding Path=DataContext.GlobalSelectedElement,ElementName=RootContainer, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
This is bound to a ViewModel:
public class MenuViewModel : SomeBaseViewModelThatHandleTheNotify
{
public IMyElement GlobalSelectedElement
{
get => GetValue<IMyElement>();
set => SetValue(value); //I NEVER COME HERE!!!)
}
public SomeCollectionContainer Collection
{
get => GetValue<SomeCollectionContainer>();
set => SetValue(value);
}
}
My sub control, has a dependency property, which is changed when the internal ViewModel of the UserControl is changed.
public IMyElement SelectedElement
{
get { return (IMyElement)GetValue(SelectedElementProperty); }
set { SetValue(SelectedElementProperty, value);/*HERE I COME!*/ }
}
public static readonly DependencyProperty SelectedElementProperty =
DependencyProperty.Register("SelectedElement", typeof(IMyElement), typeof(CollectionControl), new PropertyMetadata(null, OnSelectedElementChanged));
private static void OnSelectedElementChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
//Retrieve the sub control ViewModel and set the property
SubControlViewModel subControlViewModel = (SubControlViewModel)((CollectionControl)dependencyObject).RootContainer.DataContext;
subControlViewModel.SelectedElement = (IMyElement)dependencyPropertyChangedEventArgs.NewValue;
}
//In the constructor, I register to PropertyChanged of the ViewModel, and I set the SelectedElement when it change.
So, basically, I come in the SetValue of the dependency property of the UserControl, but I never come in the GlobalSelectedElement property of my main ViewModel.
What did I miss?
EDIT
I tried to directly use two-way binding between my ViewModel and the Dependency Property, doesn't work either:
In my sub control:
<UserControl x:Class="xxx.yyy.vvv.CollectionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:xxx.yyy.vvv.Menu"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Name="RootContainer" Orientation="Vertical">
<StackPanel.DataContext>
<local:CollectionControlViewModel/>
</StackPanel.DataContext>
<Label Content="{Binding Collection.Name}" Margin="5,0,0,0" />
<ListBox ItemsSource="{Binding Collection.Items}" HorizontalContentAlignment="Stretch" Padding="0" BorderThickness="0" SelectedItem="{Binding SelectedElement, RelativeSource={RelativeSource AncestorType={x:Type local:CollectionControl}}, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</StackPanel>
</UserControl>
I feel that my UserControl DependencyProperty is bound from 2 sides
I've tried to make a small diagram to show my classes.
So my CollectionControl.SelectedElement are correctly set, but the MenuViewModel.SelectedItem are not.
Try to bind to the DataContext of the parent ItemsControl using a RelativeSource:
<local:CollectionControl Collection="{Binding}"
SelectedElement="{Binding Path=DataContext.GlobalSelectedElement, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=TwoWay}"/>
Obviously using an ElementName doesn't work. This is because of namescopes. The CollectionControl element in the ItemTemplate is not in the same namescope as "RootContainer".

WPF multiple Instance of ViewModel in Usercontrol and Window

This is my first question on stackoverflow.
I have a Usercontrol that contains a viewModel as a staticresource. Than I have a Window that contains this UserModel. I want to send commands from the Window to the viewmodel that is used by the usercontrol. So I bound the Viewmodel class to the Usercontrol and to the Window as a static Resource. But the viewmodell ist instanciated 2 times. Once from the Window and once from the usercontrol. What am I doing wrong?
Here is the xaml of the usercontrol:
<UserControl x:Class="ScanVM.ScanView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:t="clr-namespace:ScanVM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="501.305">
<UserControl.Resources>
<t:ScanVM x:Key="ABC"/>
</UserControl.Resources>
<DockPanel DataContext="{Binding Source={StaticResource ABC}}">
<Grid ShowGridLines="True" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="10*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Label Content="Geräte:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" Margin="0,10,10,0"/>
<DataGrid ItemsSource="{Binding Path=ScanIDs, Mode=OneWay}" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" Grid.Row="1" VerticalAlignment="Top" AutoGenerateColumns="false" Height="152" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ScanIds}" ClipboardContentBinding="{x:Null}" Header="Bezeichnung" MinWidth="200"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</DockPanel>
This is the code of my window:
<Window x:Class="ScanViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ribbon="clr-namespace:System.Windows.Controls.Ribbon;assembly=System.Windows.Controls.Ribbon"
xmlns:self="clr-namespace:ScanViewer"
xmlns:t="clr-namespace:ScanVM;assembly=ScanVM"
mc:Ignorable="d"
Title="Scan-Viewer" Height="682.225" Width="694">
<Window.Resources>
<t:ScanVM x:Key="ABC"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding
Command="self:MainWindow.ExitCommand"
CanExecute="ExitCommand_CanExecute"
Executed="ExitCommand_Execute"/>
</Window.CommandBindings>
<DockPanel>
<ribbon:Ribbon DockPanel.Dock="Top">
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu Visibility="Collapsed">
<RibbonApplicationMenuItem Header="Beenden"/>
<RibbonApplicationMenu.FooterPaneContent>
<RibbonButton Label="Exit"/>
</RibbonApplicationMenu.FooterPaneContent>
</RibbonApplicationMenu>
</Ribbon.ApplicationMenu>
<RibbonTab Header="Start">
<RibbonGroup Header="Programm">
<RibbonButton Label="Beenden" Margin="0,5,0,0" Command="self:MainWindow.ExitCommand" />
</RibbonGroup>
<RibbonGroup Header="Scannen">
<RibbonButton Label="Daten Scannen" Margin="0,5,0,0" Command="{Binding ScanCmd, Source={StaticResource ABC}}"/>
</RibbonGroup>
</RibbonTab>
</ribbon:Ribbon>
<Grid DockPanel.Dock="Top" Height="464">
<t:ScanView DataContext="{Binding Source={StaticResource ABC}}"/>
</Grid>
</DockPanel>
And this is the code of my Viewmodel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Windows.Input;
using System.Threading;
using MVVMBase;
namespace ScanVM
{
public class ScanVM : ViewModelBase
{
private ICommand _doWorkCmd;
private ICommand _scanCmd;
private bool scanAllowed = false;
public List<int> ScanIDs
{
get;
set;
}
public ScanVM()
{
_doWorkCmd = new DelegateCommand((param) => DoWork());
_scanCmd = new DelegateCommand((param) => Scan());
}
public bool NWScanAllowed(object executeAllowed)
{
return scanAllowed;
}
public ICommand WorkCmd
{
get { return _doWorkCmd; }
}
public ICommand ScanCmd
{
get
{
return _scanCmd;
}
}
private void Scan()
{
scanAllowed = true;
}
private void DoWork()
{
}
}
}
The answer depends on what you want to do with the shared object.
You can either move ABC to App.xaml inside <Application.Resources> tag to share it throughout your project.
Or you can remove the one in user control and use the ABC in window's resources.
You will need a valid reference path to address the ABC in window resource from user control in this case.
which brings us to these two situations:
The valid path is possible to find:
So let's say if your user control is a part of the mentioned window (and if the window's DataContext is set to ABC) then inside the user control you will reference it like this:
<DockPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}">
The valid path is impossible to find and you just want the static resource. If you insist on doing it this way it is better to just move the resource to App.xaml than to try to tweak this out.

DataContext in WPF throws an exception

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Name="myTxt" Text="{Binding}" />
</Grid>
</Window>
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = "fdfsfds";
}
}
}
I wonder why this code isn't working? It throws an exception. What should I do to bind textBox?
The default Binding for TextBox.Text property - is TwoWay
"Two-way binding requires Path or XPath."
So, you can use OneWay Binding:
<Grid>
<TextBox Name="myTxt" Text="{Binding Mode=OneWay}" />
</Grid>
If you still want TwoWay binding you may use this code:
<TextBox Name="myTxt" Text="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}" />

Bindings inside CompositeCollection

I want to create a TabControl with a number of "static" TabItems (explicitly typed in XAML) and a number of dynamically added TabItems. To achieve this I tried to use a CompositeCollection as the TabControl.ItemSource.
Sample code:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<Window.Resources>
<x:Array x:Key="SomeTexts" x:Type="sys:String">
<sys:String>Text1</sys:String>
<sys:String>Text2</sys:String>
</x:Array>
</Window.Resources>
<TabControl>
<TabControl.ItemsSource>
<CompositeCollection>
<TabItem Header="Test">
<StackPanel>
<TextBlock x:Name="MyText" Text="Blah" />
<TextBlock Text="{Binding Text, ElementName=MyText}" />
</StackPanel>
</TabItem>
<CollectionContainer Collection="{StaticResource SomeTexts}" />
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
</Window>
This example has one fixed tab item and three "dynamic" tab items (note that 'SomeTexts' is a fixed array here just to ease the example; in the real code it will be a dynamic collection).
The example works except for the 'ElementName' binding, which does not work. I suppose this is because the CompositeCollection is not a Freezable (see also Why is CompositeCollection not Freezable?).
Does anyone has a solution?
I had a similar scenario. To solve this I found the following article.
This post explains how to create a freezable proxy object that you can set your data context to. You then add this proxy as a resource that can be referenced as a static resource. See the following:
public class BindingProxy : Freezable
{
public static DependencyProperty DataContextProperty = DependencyProperty.Register(
"DataContext", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
}
This can then be used in your xaml like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:y="clr-namespace:Namespace.Of.BindingProxy"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<Window.Resources>
<x:Array x:Key="SomeTexts" x:Type="sys:String">
<sys:String>Text1</sys:String>
<sys:String>Text2</sys:String>
</x:Array>
<y:BindingProxy x:Key="Proxy" DataContext="{Binding}" />
</Window.Resources>
<TabControl>
<TabControl.ItemsSource>
<CompositeCollection>
<TabItem Header="Test">
<StackPanel>
<TextBlock x:Name="MyText" Text="Blah" />
<TextBlock Text="{Binding DataContext.SomeProperty, Source={StaticResource Proxy}}" />
</StackPanel>
</TabItem>
<CollectionContainer Collection="{StaticResource SomeTexts}" />
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
</Window>

Why can I bind to ListBox but not to DataGrid in WPF?

I set up LINQ-to-SQL / NorthWind in WPF.
The ListBox shows data but the DataGrid doesn't (no errors, just doesn't display anything).
I referenced WPFToolkit.dll.
Why is the DataGrid not displaying the data that ListBox can?
XAML:
<Window x:Class="TestLinq343.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="ShowCustomer">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CategoryID}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding ProductName}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<dg:DataGrid x:Name="TheDataGrid" AutoGenerateColumns="True"></dg:DataGrid>
<ListBox x:Name="TheListBox" ItemTemplate="{StaticResource ShowCustomer}"/>
</Grid>
</Window>
code behind:
using System.Linq;
using System.Windows;
using TestLinq343.Model;
using Microsoft.Windows.Controls;
namespace TestLinq343
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
NorthwindDataContext db = new NorthwindDataContext();
var sortedProducts =
from p in db.Products
orderby p.UnitsInStock descending
select p;
TheDataGrid.ItemsSource = sortedProducts;
TheListBox.ItemsSource = sortedProducts;
}
}
}
It was just a XAML issue, this fixes it:
<ScrollViewer>
<StackPanel>
<dg:DataGrid x:Name="TheDataGrid"/>
<ListView x:Name="TheListView" ItemTemplate="{StaticResource ShowCustomer}"/>
</StackPanel>
</ScrollViewer>
maybe because you did not specifiy datagrid columns. try setting the datagrids AutoGenerateColumns property to true.

Resources