WPF binding does not work for an ItemsControl - c#

I am trying to bind a dependency property to another property but it does not seem to work. I have an ItemsControl within another ItemsControl as such:
<!--This is an itemscontrol in BigBox.xaml that contains bins-->
<ItemsControl
x:Name="ctrlBin"
Grid.Column="1"
Grid.Row="1"
ItemsPanel="{StaticResource HorizontalStackPanel}"
ItemsSource="{Binding Bins}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl
ItemsSource="{Binding}"
ItemContainerStyle="{StaticResource BinViewContainer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:BinView
x:Name="ctrlBin"
BorderBrush="Black"
BorderThickness="1"
Bin="{Binding}"
BinFlashStart="{Binding DashboardFlashStart}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is in my 'BigBox'
private bool? m_DashboardFlashStart;
public bool? DashboardFlashStart
{
get=> m_DashboardFlashStart;
set => Set(ref m_DashboardFlashStart, value);
}
And this is in my BinView
//This is in the BinView.xaml.cs
public static DependencyProperty BinFlashStartProperty =
DependencyProperty.Register(
"BinFlashStart",
typeof(bool?),
typeof(BinView),
new PropertyMetadata(null, OnBinFlashStartSet));
private static void OnBinFlashStartSet(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
BinVM Bin = ((BinView)sender).m_BinVM;
Bin.CurBin.FlashBin = (bool?)e.NewValue;
}
public bool? BinFlashStart
{
get => (bool?) GetValue(BinFlashStartProperty);
set => SetValue(BinFlashStartProperty, value);
}
With that i get an error
System.Windows.Data Error: 40 : BindingExpression path error: 'DashboardFlashStart' property not found on 'object' ''Bin' (HashCode=-125066214)'. BindingExpression:Path=DashboardFlashStart; DataItem='Bin' (HashCode=-125066214); target element is 'BinView' (Name=''); target property is 'BinFlashStart' (type 'Nullable`1')
Why is it looking for DashboardFlashStart property on 'Bin'. I thought that is the source which comes from the BigBox.
Like if i put a static value of "True" in BigBox.xaml for BinFlashStart instead of binding then that works. Why am i not able to bind to the DashboardFlashStart of BigBox.
Please if somebody can explain what is going on, that would be really helpful.
I am new to this WPF.

This should work provided that the DashboardFlashStart property is defined in the code-behind of the BigBox class, i.e. in BigBox.xaml.cs:
BinFlashStart="{Binding DashboardFlashStart, RelativeSource={RelativeSource AncestorType=local:BigBox}}"/>

Your control is looking for a property named DashboardFlashStart in it's datacontext. But the property isn't in the "Bin" class but in the BigBox, so what you have to do is to specify for this binding the source of the binding to the bigbox datacontext.
Change your Binding BinFlashStart="{Binding DashboardFlashStart}"
With something like this
{Binding DataContext.DashboardFlashStart, ElementName=ctrlBin}"

Related

C# WPF: Dependency Property vs MVVM

I need to create a component with Dependency Property, I don´t know how to bind the data from another user control, can someone explain it to me? I don´t think i need to create a viewmodel for the component.
Here´s my code:
Here I call the component
<DockPanel Grid.Row="1">
<ctrls:CustomComboBox Collection="{Binding Path=TestingCollection}" SelectedCollectionItem="{Binding Path=SelectedItem}"
CreateCommand="" DeleteCommand="" UpdateCommand=""/>
</DockPanel>
And Here it´s my component
<ComboBox Grid.Column="0" SelectedValue="{Binding SelectedCollectionItem,ElementName=CustomComboBoxControl, Mode=TwoWay}"
ItemsSource="{Binding Collection}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
/>
Here it is my xaml.cs
public static readonly DependencyProperty CollectionProperty =
DependencyProperty.Register("Collection", typeof(ObservableCollection<object>), typeof(CustomComboBox));
public ObservableCollection<object> Collection
{
get { return (ObservableCollection<object>)GetValue(CollectionProperty); }
set { SetValue(CollectionProperty, value); }
}
public static readonly DependencyProperty SelectedCollectionItemProperty =
DependencyProperty.Register("SelectedCollectionItem", typeof(object), typeof(CustomComboBox));
public object SelectedCollectionItem
{
get { return (object)GetValue(SelectedCollectionItemProperty); }
set { SetValue(SelectedCollectionItemProperty, value); }
}
EDIT:
I tryed creating a VM and setting the data context, and works, but dependency properties seem pointless
EDIT 2:
I succeded, i delete my VM i was lacking and ElementName in ItemSource
I succeded, i delete my VM i was lacking and ElementName in ItemSource

custom user control binding on itemspaneltemplate

I'm having trouble binding to a custom dependencyproperty on a usercontrol to my MVVM ViewModel. My user control is correctly working when i use it directly on my view:
<local:CustomControl Mode="{Binding Mode, Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="0">
<Button x:Name="InfoBox1" Content="Test1" />
<Button x:Name="InfoBox2" Content="Test2" />
</local:CustomControl>
But using it as an itemspaneltemplate the binding is not working:
<ItemsControl Grid.Row="0" ItemsSource="{Binding Equipment}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:CustomControl Mode="{Binding Mode, Mode=TwoWay}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've tried using a RelativeSource and finding the itemscontrol/view and setting the path to either Mode or DataContext.Mode but I just can't get the binding to work.
Mode is defined as:
public static readonly DependencyProperty ModeProperty;
public Modes Mode
{
get { return (Modes)this.GetValue(ModeProperty); }
set { this.SetValue(ModeProperty, value); }
}
and registered in the constructor of the custom control:
public CustomControl()
{
Mode = Modes.Default;
}
static CustomControl()
{
ModeProperty = DependencyProperty.Register("Mode", typeof(Modes), typeof(CustomControl), new FrameworkPropertyMetadata(Mode.Default, OnModeChanged));
}
private static void OnModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
CustomControl ctrl= o as CustomControl ;
if (ctrl== null) return;
Modes mode = (Modes)e.NewValue;
ctrl.Mode = mode;
}
Do I need to use a workaround to get the control working as the panel template or am I just messing up the binding too bad?
----Edit
The viewmodel part:
private Modes _mode= Modes.Default;
public Modes Mode
{
get { return _mode; }
set { _mode= value; NotifyPropertyChanged(); }
}
private ObservableCollection<EquipmentViewModel> _equipment;
public ObservableCollection<EquipmentViewModel> Equipment
{
get { return _equipment; }
set { _equipment = value; NotifyPropertyChanged(); }
}
----Edit2:
I've investigated further and I'm more complexed. I've added the following to both the ItemsPanelTemplate's control and the one directly in the grid.
Visibility="{Binding Visible, Converter={StaticResource visibilityConverter}}"
Altering this Visible boolean works in both cases. So it appears to only be an issue with the custom DependencyProperty.
Inspecting the visual tree the DataContext of the control as ItemsPanelTemplate is also correct.
What could make the dependency property work properly when used straight and not when used as an itemspaneltemplate ?
Found what was causing the strange conflicting behavior.
I was setting the property to a certain value in the normal ctor
public CustomControl()
{
Mode = Modes.Default;
}
This was appearently causing a conflict when using the control as an Itemspaneltemplate. Removing this made the binding work as expected.
I guess the difference in behavior has something to do with the calls to the constructor at different times ?

Custom Control DataContext Doesn't Work

I have a Custom Controller that works fine like this:
<Controller:DLBox ID="{Binding SHVM.Selected.ID}" />
I mean binding ID to a property in ViewModel. But when I want to bind it like this:
<ScrollViewer DataContext="{Binding SHVM.Selected}">
<Controller:DLBox ID="{Binding ID}" />
</ScrollViewer>
Binding to DataContext of parent, It doesn't work at all. I have some other Custom Controllers and they do fine, But I don't know what the hell is this one's problem!
This is the controller:
public partial class DLBox : UserControl
{
public static DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(int), typeof(DLBox),
new FrameworkPropertyMetadata(0,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => (o as DLBox).IDPropertyChanged((int)e.NewValue)));
public int ID { get; set; }
private void IDPropertyChanged(int e)
{
ID = e;
}
}
Could someone please tell my what's wrong? Because I'm debugging for 6 hours straight and didn't find anything! Thanks a lot.
‌
‌
UPDATE:
That worked with just adding:
<... DataContext={Binding} .../>
And I don't know why!
Now the real problem is that I want to use this inside 2 ItemsControls and even with DataContext Still doesn't work.
(Just for clarification, I have 2 Lists inside each other. Think about the first one like 10 schools First List, and inside each school there is some studens Second List)
<ItemsControl ItemsSource="{Binding Source={StaticResource Locator}, Path=SHVM.Extra.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl DataContext="{Binding}" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Controller:DLBox DataContext="{Binding}" ID="{Binding ID}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<ItemsControl.ItemTemplate>
</ItemsControl>
UPDATE 2:
ID is just a TextBlock In a UserControl. There is nothing that I can show here!
All I did, Just set the Text of TextBlock inside the PropertyCallBack (Didn't use MVVM inside my controllers):
<TextBlock x:Name="txtIDValue"/>
And inside CodeBehind:
private void IDPropertyChanged(string e)
{
ID= e;
txtIDValue.Text = e;
}
There is nothing relevant to this problem, And that's why I couldn't figure it out!
Appreciate any help.
ANSWER:
After 12 hours working on it, I find out that It was an idiotic mistake! I don't know why and when I set DataContext in my Controller's XAML!
Anyway, Thanks.
Your dependency property declaration is wrong, because the getter and setter of the CLR wrapper must call the GetValue and SetValue methods respectively. Besides that, your PropertyChangedCallback is redundant. There is no need to set the property again in a callback that is called when the property value has just been set.
The declaration should look like this:
public static readonly DependencyProperty IDProperty = DependencyProperty.Register(
"ID", typeof(int), typeof(DLBox),
new FrameworkPropertyMetadata(
0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public int ID
{
get { return (int)GetValue(IDProperty); }
set { SetValue(IDProperty, value); }
}

How to bind the datacontext of a tabcontrol content to an instance of an viewmodel in a observablecollection

I want to bind the content of my TabControl to an instance of my StepViewModel in a ObservableCollection Steps.
My ProcessViewModel:
pubic class ProcessViewModel : ViewModelBase
{
public ObservableCollection<StepViewModel> Steps
{
get { return _steps; }
set { _steps = value; OnPropertyChanged("Steps"); }
}
public StepViewModel SelectedStep
{
// like above...
}
}
My StepViewModel (DataContext should be the StepVMs in Steps of ProcessVM):
public class StepViewModel : ViewModelBase
{
public string Name { get {...} set {...} }
public object Media { get {...} set {...} }
//...
}
My TabControl (DataContext is ProcessViewModel):
<C1:C1TabControl
ItemsSource="{Binding Steps}"
SelectedItem="{Binding SelectedStep}"
SelectionChanged="{tcSteps_OnSelectionChanged">
<C1:C1TabControl.ContentTemplate>
<DataTemplate>
<local:StepView
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type vmns:ProcessViewModel}}, Path=SelectedStep}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
</local:StepView>
</DataTemplate>
</C1:C1TabControl.ContentTemplate>
</C1:C1TabControl>
The Compiler delivers the following output message:
"System.Windows.Data Error: 40 : BindingExpression path error:
'SelectedStep' property not found on 'object' ''StepViewModel'
(HashCode=32952144)'. BindingExpression:Path=SelectedStep;
DataItem='StepViewModel' (HashCode=32952144); target element is
'StepView' (Name='StepView'); target property is 'DataContext' (type
'Object')"
Does anyone know how I can solve the?
Thanks!
It looks like there could be a few small issues with the RelativeSource on your DataContext binding for the StepView. Since the template is not part of the visual tree, I don't think you can use FindAncestor. You can use a StaticResource as a pointer to your main DataContext (eg http://www.codeproject.com/Articles/27432/Artificial-Inheritance-Contexts-in-WPF), but I think it might be simpler to just search by ElementName instead in this case. That method would look something like this:
Update your TabControl to have a name, so it is searchable in bindings by ElementName
<C1:C1TabControl
x:Name="MyTabControl"
ItemsSource="{Binding Steps}"
SelectedItem="{Binding SelectedStep}"
SelectionChanged="{tcSteps_OnSelectionChanged">
Update your StepView to look for the TabControl's DataContext by ElementName
<local:StepView DataContext="{Binding ElementName=MyTabControl, Path=DataContext.SelectedStep}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
</local:StepView>

WPF Custom Control Picking Up Parent Window's DataContext

I have a ViewModel
public class ViewModel:ViewModelObject
{
public ViewModel()
{
ProjectionDataElementList = new ObservableCollection<ProjectionDataElement>();
}
public ObservableCollection<ProjectionDataElement> ProjectionDataElementList { get; set; }
private ProjectionDataElement _currentSelectedProjectionDataElement;
public ProjectionDataElement CurrentSelectedProjectionDataElement
{
get
{
return _currentSelectedProjectionDataElement;
}
set
{
_currentSelectedProjectionDataElement = value;
OnPropertyChanged("CurrentSelectedProjectionDataElement");
}
}
}
A control called ProjectorDisplayControl
<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
..................>
<Viewbox>
<StackPanel>
<TextBlock Text="{Binding Path=TextBody}"/>
</StackPanel>
</Viewbox>
public partial class ProjectorDisplayControl : UserControl
{
public ProjectorDisplayControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ProjectedDataProperty = DependencyProperty.Register("ProjectedData", typeof(ProjectionDataElement), typeof(ProjectorDisplayControl),
new PropertyMetadata(new PropertyChangedCallback((objectInstance, arguments) =>
{
ProjectorDisplayControl projectorDisplayControl = (ProjectorDisplayControl)objectInstance;
projectorDisplayControl._projectedData = (ProjectionDataElement)arguments.NewValue;
})));
public ProjectionDataElement ProjectedData
{
get
{
return (ProjectionDataElement)GetValue(ProjectedDataProperty);
}
set
{
SetValue(ProjectedDataProperty, value);
}
}
private ProjectionDataElement _projectedData
{
get
{
return this.DataContext as ProjectionDataElement;
}
set
{
this.DataContext = value;
}
}
}
That control is used in two places.
First place is in a ListBox where it works great:
<ListBox Grid.Column="0" ItemsSource="{Binding Path=ProjectionDataElementList}" Name="projectedDataListBox"
SelectedItem="{Binding Path=CurrentSelectedProjectionDataElement, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border BorderThickness="1" BorderBrush="Black">
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding}"/>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Second place is on top of a form where I set the form's DataContext with a ViewModel object. To make the ProjectorDisplayControl consume the CurrentSelectedProjectionDataElement in the ViewModel I would expect to have to do this:
<Window x:Class="Fast_Project.DisplayWindow"
................>
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding CurrentSelectedProjectionDataElement}"/>
</StackPanel>
</Viewbox>
That code gives me two binding errors:
System.Windows.Data Error: 40 :
BindingExpression path error:
'TextBody' property not found on 'object' ''ViewModel' (HashCode=2512406)'.
BindingExpression:Path=TextBody;
DataItem='ViewModel' (HashCode=2512406);
target element is 'TextBlock' (Name='');
target property is 'Text' (type 'String')
System.Windows.Data Error: 40 :
BindingExpression path error:
'CurrentSelectedProjectionDataElement' property not found on 'object' ''ProjectionDataElement' (HashCode=37561097)'.
BindingExpression:Path=CurrentSelectedProjectionDataElement;
DataItem='ProjectionDataElement' (HashCode=37561097);
target element is 'ProjectorDisplayControl' (Name='_projectorDisplay');
target property is 'ProjectedData' (type 'ProjectionDataElement')
When I watch the setter of the private property _projectedData on ProjectorDisplayControl which sets the DataContext I first see it get set with a valid value and then set to null.
In the DisplayWindow that holds the single ProjectorDisplayControl I can remove the binding to the CurrentSelectedProjectionDataElement and then I only get the first binding error message:
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" />
</StackPanel>
</Viewbox>
The first binding error makes me feel like the ProjectorDisplayControl's DataContext is getting set with a ViewModel object when the DisplayWindow's DataContext is getting set. But from what I've read controls don't share the same data context with their parent window unless you set it so.
I've treating the binding path for the ProjectorDisplayControl.ProjectedData in DisplayWindow like it was a ProjectionDataElement object as the second error message states.
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding }"/>
</StackPanel>
</Viewbox>
Then is tells me:
Cannot create default converter to perform 'one-way' conversions between types 'Fast_Project.ViewModel' and 'Fast_Project.ProjectionDataElement'
Like it really was the ViewModel object like I thought it was in the first place...
Anyways I suspect that the root of my problem lies in the how I see the ProjectorDisplayControl having a DataContext set to the ViewModel object. Anyone see where I messed up?
Thank you all for your help!
The solution was:
ProjectorDisplayControl
<StackPanel>
<TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:ProjectorDisplayControl}}}"/>
</StackPanel>
DisplayWindow
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding Path=ViewModel.CurrentSelectedProjectionDataElement,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:DisplayWindow}}}"/>
</StackPanel>
Child control inherits the Dependency property value (or DataContext in this case) from their Parents.
When you are using the UserControl inside the itemTempalte, then the DataContext of each item is already ProjectionDataElement and hence the DataContext of your control is set to ProjectionDataElement.
When you are using the control inside parent it will inherit its DataContext.
The problem is that you are setting your ProjectedData property on control and not using it inside it. If you want that each of your control should be bind to the value set on ProjectedData then you should update the bindings like:
<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
..................>
<Viewbox>
<StackPanel>
<TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Self}"/>
</StackPanel>
For you second error, you must be setting the DataContext of that window to ProjectionDataElement somewhere that is why is searching it inside it.

Categories