Custom Control DataContext Doesn't Work - c#

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); }
}

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

WPF binding does not work for an ItemsControl

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}"

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 ?

RelayCommand not getting the right Model

I created a user control that looks like a tile. Created another user control named TilePanel that serves as the default container of the tiles. And lastly, the very UI that looks like a Window start screen. I used RelayCommand to bind my TileCommands
Here are the codes:
Tilev2.xaml
<UserControl x:Class="MyNamespace.Tilev2"
Name="Tile"....
>
...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" Command="{Binding ElementName=Tile, Path=TileClickCommand}" >
</Button>
</UserControl>
Tilev2.xaml.cs
public partial class Tilev2 : UserControl
{
public Tilev2()
{
InitializeComponent();
}
//other DPs here
public ICommand TileClickCommand
{
get { return (ICommand)GetValue(TileClickCommandProperty); }
set { SetValue(TileClickCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for TileClickCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TileClickCommandProperty =
DependencyProperty.Register("TileClickCommand", typeof(ICommand), typeof(Tilev2));
}
}
Then I created a TilePanel user control as the container of the tiles
TilePanel.xaml
<UserControl x:Class="MyNamespace.TilePanel"
...
>
<Grid>
<ScrollViewer>
<ItemsControl Name="tileGroup"
ItemsSource="{Binding TileModels}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local2:Tilev2 TileText="{Binding Text}"
TileIcon="{Binding Icon}"
TileSize="{Binding Size}"
TileFontSize="{Binding FontSize}"
Background="{Binding Background}"
TileCaption="{Binding TileCaption}"
TileCaptionFontSize="{Binding TileCaptionFontSize}"
TileClickCommand="{Binding TileCommand}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
TilePanel.xaml.cs
public partial class TilePanel : UserControl
{
public TilePanel()
{
InitializeComponent();
DataContext = new TilePanelViewModel();
}
public TilePanelViewModel ViewModel
{
get { return (TilePanelViewModel)this.DataContext; }
}
}
My ViewModel for TilePanel
TilePanelViewModel.cs
public class TilePanelViewModel : ViewModelBase
{
private ObservableCollection _tileModels;
public ObservableCollection<TileModel> TileModels
{
get
{
if (_tileModels == null)
_tileModels = new ObservableCollection<TileModel>();
return _tileModels;
}
}
}
Then my Tile model
TileModel.cs
public class TileModel : BaseNotifyPropertyChanged
{
//other members here
ICommand tileCommand { get; set; }
//other properties here
public ICommand TileCommand
{
get { return tileCommand; }
set { tileCommand = value; NotifyPropertyChanged("TileCommand"); }
}
}
}
This is my StartScreen View where TilePanels with tiles should be displayed...
StartScreen.xaml
<UserControl x:Class="MyNamespace.StartMenu"
... >
<Grid>
<DockPanel x:Name="dockPanel1" Grid.Column="0" Grid.Row="1" Margin="50,5,2,5">
<local:TilePanel x:Name="tilePanel"></local:TilePanel>
</DockPanel>
</Grid>
</UserControl>
StartScreen.xaml.cs
public partial class WincollectStartMenu : UserControl, IView<StartMenuViewModel>
{
public WincollectStartMenu()
{
InitializeComponent();
}
public StartMenuViewModel ViewModel { get { return (DataContext as StartMenuViewModel); } }
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ViewModel.Tile = tilePanel.ViewModel.TileModels;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
return;
}
}
In my start screen ViewModel, I used ObservableCollection Tile
and use Tile.Add(tile); to populate my start screen with Tiles inside the TilePanel...
StartMenuViewModel.cs
TileModel tile = new TileModel() { Text = "Testing1", FontSize = 11, Size = TileSize.Medium, Background = (SolidColorBrush)new BrushConverter().ConvertFromString("#039BE5"), Tag="Something" };
tile.TileCommand = new RelayCommand(
p => Tile_TileClick(tile.Tag),
p => true
);
temp.Add(tile);
Now the problem is, if I add a new code below, tile = new TileModel() {...}
tile.TileCommand = new RelayCommand(...), even if I clicked on the first tile, my Tile_TileClick() will get the second tile's info (or the last tile inserted)...
Am I doing something wrong? Or Im doing everything wrong...?
This is not direct answer to your question, but hopefully it will give you few thoughts.
Ok, first of all, don't name your usercontrol like this:
<UserControl x:Class="MyNamespace.Tilev2" Name="Tile"/>
because the name can be easily overriden when using the usercontrol somewhere:
<local:Titlev2 Name="SomeOtherName" />
and the binding inside Tilevs with ElementName won't work: Command="{Binding ElementName=Tile, Path=TileClickCommand}"
Second, what's the point of Tilev2 usercontrol? Why don't just put the button directly to the DataTemplate inside TilePanel class?
If you need to reuse the template, you can put the template to resource dictionary.
If you need some special presentation code in the Tilev2 codebehind or you need to use the Tilev2 without viewmodel, it's better to create custom control instead of usercontrol in this case. it has much better design time support, and writing control templates it's easier (Triggers, DataTriggers, TempalteBinding, etc). If you used custom Control insead UserControl, you wouldn't have to write {Binding ElementName=Tile, Path=TileClickCommand}, or use RelativeSource, etc.
Third, it seems like you forced MVVM pattern where you can't really take advantage of it. Point of MVVM is separate application logic from presentation. But your Tile and TilePanel usercontrols are just presentation. You application logic could be in StartScreen which is concrete usage of TileName.
I would create custom controls called TilePanel (potentionally inherited from ItemsControl, Selector or ListBox) and if needed also for Tile. Both controls should not be aware of any viewmodels. There's absolutelly no need for that.
Take ListBox as an example. ListBox does not have viewmodel but can be easily used in MVVM scenarios. Just because ListBox it is not tied to any viewmodel, it can be databound to anything.
Just like ListBox creates ListBoxItems, or
Combobox creates ComboBoxItems, or
DataGrid creates DataGridRows or
GridView (in WinRT) creates GridViewRow, your TilePanel could create Tiles.
Bindings to tile specific properties, like Icon or Command could be specified in TilePanel.ItemContainerStyle orusing simillar appriach like DisplayMemberPath, resp ValueMemberPath in ListBox.
final usage could the look like:
<TilePanel ItemsSource="{Bidning ApplicationTiles}" />
or
<TilePanel>
<Tile Icon=".." Command=".." Text=".." />
<Tile Icon=".." Command=".." Text=".." />
</TilePanel>
Last, the name `TilePanel' evoked that it is some kind of panel like StackPanel, WrapPanel, etc. In other words, it is FrameworkElement inherited from Panel.
TilesView would be more suitable name for the control than TilePanel. The -View postfix is not from MVVM, it just follows naming convention -GridView, ListView...
Saw the problem...
To pass a parameter from button, I used CommandParameter so I could use it in switch-case scenario to know which button was clicked. But still, param was still null...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
TileCommand = new MyCommand() { CanExecuteFunc = param => CanExecuteCommand(), ExecuteFunc = param => Tile_TileClick(param)}
After 2 whole damn days, I changed it:
From this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding Tag, ElementName=Tile}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
To this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
My first post does error because CommandParameter does not know where to get its DataContext so I replaced it to CommandParameter={Binding} so it will get whatever from the DataContext.

WPF Data Binding an ObservableCollection (or LinkedList) to a WrapPanel

Ok. First of all, I've looked at a bunch of other questions and a ton of other places on the internet on how to do this, and none of them are helping, so please don't mark this as a duplicate, and also, heads up cause I probably made a really stupid mistake.
I'm trying to bind an ObservableCollection to a WrapPanel using an ItemsControl and a DataTemplate. The following is my XAML Code:
<ItemsControl x:Name="wPanel">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--<Border BorderBrush="DarkGray" Background="Transparent">-->
<StackPanel MinWidth="250">
<Grid>
<TextBlock Text="{Binding address}" />
</Grid>
<Grid>
</Grid>
</StackPanel>
<!--</Border>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note: I did have (for the ItemsSource property of ItemsControl) {Binding properties}
This is my declaration of properties:
public ObservableCollection<Property> properties = new ObservableCollection<Property>();
And the Property Class is the following plus many more properties:
private string address { get; set; }
private string city { get; set; }
private string postcode { get; set; }
private double price { get; set; }
private LinkedList<Tennant> tennants { get; set; }
...
I thought I had solved the problem with this,
Binding binding = new Binding();
binding.Source = properties;
wPanel.SetBinding(ItemsControl.ItemsSourceProperty, binding);
But, then the line in the xaml: <TextBlock Text="{Binding address}" /> didn't work.
Then I came to the conclusion that it had to do with the properties object, and how it wouldn't bind unless I did it through code.
What am I doing wrong, for it not bind through XAML, etc.? What do I need to change about the properties object, or what do I need to do?
Thanks in advance.
You can bind ItemsControl's ItemsSource to properties just like #AjS answer. But before that, you need to change properties declaration to be property instead of field.
public ObservableCollection<Property> properties { get; set; }
And also address property of your Property class need to be public.
public string address { get; set; }
Isn't the 'properties' a property on the window?
If you are binding in xaml, make sure you use declare 'properties' as 'Property', set datacontext of window to itself and then set binding path:-
<ItemsControl x:Name="wPanel" ItemsSource="{Binding properties}">
this.DataContext=this; //set datacontext on parent window or control
If you are doing it in code, setting the ItemsSource directly on wPanel should work:-
wPanel.ItemsSource=properties;
This is easy trick for check Binding. Every WPF developer must know this. For example:
<TextBlock Text="{Binding}" />
Run and see it.
If TextBlock has any object in DataContext, object class name displayed in TextBlock.
If DataContext has empty. TextBlock is Empty

Categories