WPF binding to a Usercontrol and getting an error - c#

Im starting with WPF, sorry if i cant explain well, and i have hours trying to solve how to bind a collection to a custom grid is named PagingDataGrid.
The PagingDataGrid is in CustomerSearchControl binding GridItems to ItemsSource, when i excecute SearchCommand GridItems gets updated but nothing else changes.
I get the following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'GridItems' property not found on 'object' ''PagingDataGridViewModel' (HashCode=54151655)'. BindingExpression:Path=GridItems; DataItem='PagingDataGridViewModel' (HashCode=54151655); target element is 'PagingDataGrid' (Name='Me'); target property is 'ItemsSource' (type 'IEnumerable')
CustomerSearchControl:
<UserControl x:Class="Namespace.CustomerSearchControl"
... >
<Control.DataContext>
<Binding Path="CustomerSearchViewModel" ... />
</Control.DataContext>
<DockPanel LastChildFill="True">
<GroupBox Header="Registros">
<controls:PagingDataGrid ItemsSource="{Binding GridItems}" Height="300" />
</GroupBox>
</DockPanel>
</UserControl>
public class CustomerSearchViewModel : ViewModelBase
{
public ObservableCollection<GridItem> GridItems{ get; set; }
public ICommand SearchCommand { get; set; }
public CustomerSearchViewModel()
{
GridItems = new ObservableCollection<GridItem>();
SearchCommand = new RelayCommand(SearchEntities, () => true);
}
}
PagingDataGrid:
<UserControl x:Class="Namespace.PagingDataGrid" x:Name="Me"
... >
<UserControl.DataContext>
<Binding Path="PagingDataGridViewModel" ... />
</UserControl.DataContext>
<Grid>
...
<xcdg:DataGridControl
ItemsSource="{Binding ElementName=Me, Path=ItemsSource}" Grid.Row="0"/>
</Grid>
</UserControl>
public partial class PagingDataGrid : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty
= DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(PagingDataGrid),
new PropertyMetadata(default(IEnumerable)));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
}

You need to declare instance of CustomerSearchViewModel in XAML and bind to DataContext.
This is how to do it:
<UserControl.DataContext>
<local:CustomerSearchViewModel/>
</UserControl.DataContext>
Make sure to declare namespace local at root i.e. at UserControl:
xmlns:local="clr-namespace:WpfApplication" <-- Replace WpfApplication with
actual namespace of your ViewModel.
Not needed since getting instance from ServiceLocator.
And for binding to GridItems you need to bind explicitly to CustomerSearchControl DataContext using RelativeSource. This is needed because you have explicitly set DataContext on PagingDataGrid to PagingDataGridViewModel. So, it will search for GridItems property in PagingDataGridViewModel instead of CustomerSearchViewModel.
<controls:PagingDataGrid ItemsSource="{Binding DataContext.GridItems,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl}}"/>
Or you can give x:Name to CustomerSearchControl and bind using ElementName.

never set the datacontext of your usercontrol to self.
so simply remove
<UserControl.DataContext>
<Binding Path="PagingDataGridViewModel" ... />
</UserControl.DataContext>
EDIT:
if you not remove this, then your
<UserControl x:Class="Namespace.PagingDataGrid" x:Name="Me">
<UserControl.DataContext>
<Binding Path="PagingDataGridViewModel" ... />
</UserControl.DataContext>
<Grid>
<xcdg:DataGridControl ItemsSource="{Binding ElementName=Me, Path=ItemsSource}" Grid.Row="0"/>
</Grid>
</UserControl>
datacontext for your usercontrol is PagingDataGridViewModel and if PagingDataGridViewModel dont have a property ItemsSource you get an error. you never get the GridItems from your CustomerSearchViewModel that you want.

Related

How to unselect a listview item when creating a new item collection? Im using observable collection

Im having a hard time with the following error:
I have a listview that is binded to an observable collection.
Lets say it looks like this:
XAMl:
<ListView ItemsSource="{Binding myCollection}" SelectedItem="{Binding selectedItem}">
ViewModel:
private Field selecteditem;
public Field selectedItem {
get { return selecteditem; }
set
{
selecteditem = value;
}
... //other code parts
myCollection = customClass.fillCollection(selectedLightColor, selectedDarkColor);
When i click on an item it is selected. When i click on another that is the selected one. This is totally okay. However at a certain point i need to recreate the whole observable collection that is connected to this listview.
If i didnt select anything it recreates the collection perfectly.
But, when i have a selected item it throws a System.NullReferenceException error to the property that is binded to the SelectedItem of the listview.
For the recreation im using the same code mentioned above (myCollection = customClass...)
I cant find a solution that solves the problem.
I have tried myCollection.Clear() and also selectedItem = null, but the error remained the same.
Im glad to hear any help!
I tried to reproduce the problem you describe, but it didn't work for me.
My example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core2022.SO.freasy
{
public class Field
{
public int Number { get; } = random.Next();
private static readonly Random random = new Random();
public static IEnumerable<Field> GetRandomFields()
=> Enumerable.Range(0, random.Next(10, 20)).Select(_ => new Field()).ToList().AsReadOnly();
}
}
using Simplified;
using System.Collections.Generic;
namespace Core2022.SO.freasy
{
public class FieldsViewModel : BaseInpc
{
private IEnumerable<Field> _fields = Field.GetRandomFields();
private RelayCommand? _refreshFields;
public IEnumerable<Field> Fields { get => _fields; set => Set(ref _fields, value); }
public RelayCommand RefreshFields => _refreshFields
??= new RelayCommand(_ => Fields = Field.GetRandomFields());
private Field? _selectedField;
public Field? SelectedField
{
get => _selectedField;
set => Set(ref _selectedField, value);
}
}
}
<Window x:Class="Core2022.SO.freasy.FieldsWindow"
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:local="clr-namespace:Core2022.SO.freasy" xmlns:sys="clr-namespace:System;assembly=netstandard"
mc:Ignorable="d"
Title="FieldsWindow" Height="450" Width="800">
<Window.DataContext>
<local:FieldsViewModel/>
</Window.DataContext>
<Window.Resources>
<sys:String x:Key="null">No Selected Field</sys:String>
</Window.Resources>
<UniformGrid Columns="2">
<ListBox x:Name="listBox" ItemsSource="{Binding Fields}"
DisplayMemberPath="Number"
SelectedItem="{Binding SelectedField}"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<UniformGrid Columns="1">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Text>
<PriorityBinding>
<Binding Path="SelectedField.Number" Mode="OneWay"/>
<Binding Source="{StaticResource null}"/>
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Text>
<PriorityBinding>
<Binding Path="SelectedItem.Number" Mode="OneWay" ElementName="listBox"/>
<Binding Source="{StaticResource null}"/>
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
<Button Content="Refresh Collection" Command="{Binding RefreshFields}"
VerticalAlignment="Center" HorizontalAlignment="Center" Padding="15 5"/>
</UniformGrid>
</UniformGrid>
</Window>
Perhaps you missed an important detail of your implementation in the explanations, which is the cause of the error you described.
Try changing my simple example to reproduce your problem.
BaseInpc and RelayCommand classes.
Source Code Archive: freasy.7z
Because the property setter called a method which used my private variable (selecteditem) the program couldnt handle the selecteditem variable to be null!
I can finally recreate my collection by adding a statement to the setter:
private Field selecteditem;
public Field selectedItem {
get { return selecteditem; }
set
{
selecteditem = value;
if (selecteditem != null)
{
//call method and do stuffs
}
}

How to properly bind a DependencyProperty to a CollectionViewSource?

I am beginner to WPF and I am facing problem while trying to bind a Dependency Property as the source of a CollectionViewSource.
The user control exposes a DependencyProperty of type List. It is used to present the data in a DataGrid with the help of CollectionViewSource (using it for Filtering, Grouping and Sorting operations).
My MainWindow XAML:
<Window>
<local:CustomUserControl x:Name="CustomUCDataGrid" ListToDisplay="{Binding listFromDB}"/>
<Window>
My MainWindow.cs:
public partial class MainWindow : Window
{
public List<customType> listFromDB{get;set;}
public MainWindow{
listFromDB = GetListFromDB();
InitializeComponent();
this.DataContext = this;
}
}
CustomUserControl.xaml looks something like:
<UserControl x:Name="ParentNode">
<DataGrid DataContext="{Binding ElementName=ParentNode}">
<StackPanel>
<DataGrid x:Name="DirectDataGrid" ItemSource="{Binding ListToDisplay}"/>
<DataGrid x:Name="DataGridWithCVS" ItemsSource="{Binding cvsList.View}"/>
</StackPanel>
</DataGrid>
</UserControl>
CustomUserControl.xaml.cs looks like:
public partial class CustomUserControl: UserControl
{
public List<customType> ListToDisplay{
get { return (List<customType>)GetValue(ListToDisplayProperty); }
set { SetValue(ListToDisplayProperty, value); }
}
public static readonly DependencyProperty ListToDisplayProperty=
DependencyProperty.Register("ListToDisplay", typeof(List<customType>),
typeof(CustomUserControl));
public CollectionViewSource cvsList { get; set; }
public CustomUserControl{
InitializeComponent();
cvsList = new CollectionViewSource();
cvsList.Source = ListToDisplay;
DataGridWithCVS.ItemsSource = CollectionViewSource.GetDefaultView(cvsList);
}
}
Here the DataGrid with name "DirectDataGrid" has no problem in displaying the data supplied to it from the MainWindow, but the DataGrid with name "DataGridWithCVS" doesn't display any data. Couldn't find any errors while debugging.
Things I have already Tried:
Define the CollectionViewSource as a StaticResource inside the CustomUserControl XAML - Can't implement this because, the UserControl doesn't use the DataContext set from the MainWindow. It uses its own DataContext without overriding the MainWindow's DataContext. (<UserControl x:Name="ParentNode"> <DataGrid DataContext="{Binding ElementName=ParentNode}">...).
It's just some kind of madness :)
Leave Code Behind alone.
In your case, apart from declaring DependecyProperty, there should be nothing there.
<UserControl x:Name="ParentNode">
<UserControl.Resources>
<CollectionViewSource x:Key="cvsList"
Source="{Binding ListToDisplay, ElementName=ParentNode}"/>
</UserControl.Resources>
<StackPanel>
<DataGrid x:Name="DirectDataGrid" ItemsSource="{Binding ListToDisplay, ElementName=ParentNode}"/>
<DataGrid x:Name="DataGridWithCVS" ItemsSource="{Binding Mode=OneWay, Source={StaticResource cvsList}}"/>
</StackPanel>
</UserControl>

My WPF custom control's Data Context is superseding parent's

In my main window, I try to bind to a bool, but it's looking in my custom control's DataContext instead. If I don't assign DataContext in the user control, then the main window's bindings works, but (obviously) this brakes the bindings in the user control.
Here's the error:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyControlVisible' property not found on 'object' ''MyUserControlModel' (HashCode=1453241)'. BindingExpression:Path=MyControlVisible; DataItem='MyUserControlModel' (HashCode=1453241); target element is 'MyUserControl' (Name='_myUserControl'); target property is 'Visibility' (type 'Visibility')
I need binding to work on both controls, but I don't want the user control's DataContext to supersede the window's.
Here's the code:
<Window x:Class="Sandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:Sandbox.Controls" Title="Sandbox">
<DockPanel LastChildFill="True">
<DockPanel.Resources>
<BooleanToVisibilityConverter x:Key="boolToVis" />
</DockPanel.Resources>
<Grid>
<Controls:MyUserControl x:Name="_myUserControl" Visibility="{Binding MyControlVisible, Converter={StaticResource boolToVis}}"/>
</Grid>
</DockPanel>
</Window>
namespace Sandbox
{
public partial class MainWindow
{
private MainWindowModel model;
public MainWindow()
{
InitializeComponent();
DataContext = model = new MainWindowModel();
_myUserControl.Initialize(model.MyUControlModel);
}
}
}
using System.ComponentModel;
using Sandbox.Controls;
namespace Sandbox
{
public class MainWindowModel : BaseModel
{
public MyUserControlModel MyUControlModel { get; set; }
public bool MyControlVisible { get; set; }
public MainWindowModel()
{
MyUControlModel = new MyUserControlModel();
MyControlVisible = false;
OnChange("");
}
}
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChange(string s)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(s));
}
}
}
}
<UserControl x:Class="Sandbox.Controls.MyUserControl"
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"
mc:Ignorable="d">
<Grid>
<TextBlock Text="{Binding MyBoundText}"/>
</Grid>
</UserControl>
namespace Sandbox.Controls
{
public partial class MyUserControl
{
public MyUserControl()
{
InitializeComponent();
}
public void Initialize(MyUserControlModel context)
{
DataContext = context;
}
}
}
namespace Sandbox.Controls
{
public class MyUserControlModel : BaseModel
{
public string MyBoundText { get; set; }
public MyUserControlModel()
{
MyBoundText = "Hello World!";
OnChange("");
}
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded in.
In the case of your binding, normally the DataContext would be inherited so the Visibility binding could find the property MyControlVisible on the current DataContext, however because you hardcoded the DataContext in your UserControl's constructor, that property is not found.
You could specify a different binding source in your binding, such as
<Controls:MyUserControl Visibility="{Binding
RelativeSource={RelativeSource AncestorType={x:Type Window}},
Path=DataContext.MyControlVisible,
Converter={StaticResource boolToVis}}" ... />
However that's just a workaround for the problem for this specific case, and in my view is not a permanent solution. A better solution is to simply not hardcode the DataContext in your UserControl
There are a few different ways you can do depending on your UserControl's purpose and how your application is designed.
You could create a DependencyProperty on your UserControl to pass in the value, and bind to that.
<Controls:MyUserControl UcModel="{Binding MyUControlModelProperty}" ... />
and
<UserControl x:Class="Sandbox.Controls.MyUserControl"
ElementName=MyUserControl...>
<Grid DataContext="{Binding UCModel, ElementName=MyUserControl}">
<TextBlock Text="{Binding MyBoundText}"/>
</Grid>
</UserControl>
Or you could build your UserControl with the expectation that a specific property will get passed to it in the DataContext. This is normally what I do, in combination with DataTemplates.
<Controls:MyUserControl DataContext="{Binding MyUControlModelProperty}" ... />
and
<UserControl x:Class="Sandbox.Controls.MyUserControl"...>
<Grid>
<TextBlock Text="{Binding MyBoundText}"/>
</Grid>
</UserControl>
As I said above, I like to use DataTemplates to display my UserControls that expect a specific type of Model for their DataContext, so typically my XAML for the main window would look something like this:
<DataTemplate DataType="{x:Type local:MyUControlModel}">
<Controls:MyUserControl />
</DataTemplate>
<ContentPresenter Content="{Binding MyUControlModelProperty}" ... />

How to bind a Combobox ItemSource to a Property not in the DataContext?

I have a Dialog box, ConfigSetup that has a Combobox. Its data context is set to the viewModel, but I need to bind the ItemSource of my Combobox to a property in the main window( MainWindow).
public partial class MainWindow : Window, INotifyPropertyChanged
{
...
public CfgData.TMicMode[] MicModeOptions
{
get
{
return (CfgData.TMicMode[])System.Enum.GetValues(typeof(CfgData.TMicMode));
}
}
}
Here's where the viewModel is setup in the dialog box code
public partial class ConfigSetup : Window, INotifyPropertyChanged
{
private ConfigSetupVM vm_ = null;
public ConfigSetup(CfgData cfgData)
{
vm_ = new ConfigSetupVM(cfgData);
InitializeComponent();
vm_.RequestClose += delegate
{
Close();
};
DataContext = vm_;
}
}
Here's the code in the VM that has the selectedvalue property to bind to
class ConfigSetupVM : ViewModelBase, IDataErrorInfo
{
...
/// <summary>
/// C-5000's microphone mode.
/// </summary>/
public CfgData.TMicMode MicMode
{
get { return model_.MicMode; }
set { model_.MicMode = value; NotifyPropertyChanged("MicMode"); }
}
Here's the XAML with the combobox
<Window x:Class="RpP25.ConfigSetup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:RpWin="clr-namespace:RpP25"
Title="FCT Configuration"
Width="300"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow"
FocusManager.FocusedElement="{Binding ElementName=name}"
Background="AliceBlue" >
<Window.Resources>
...
</Window.Resources>
...
<ComboBox Grid.Row="6" Grid.Column="1"
HorizontalAlignment="Right" MinWidth="75"
ItemsSource="{Binding RpWin:MainWindow.MicModeOptions, Mode=OneWay}"
SelectedValue="{Binding RpWin:MainWindow.MicMode, Mode=TwoWay, TargetNullValue=Not Selected,
ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" />
...
I know I'm missing something fundamental to Binding, but I can't for the life of figure out how to bind to something outside the datacontext.
I've tried to use FindAncestor... with no success
You help would be greatly appreciated.
There are two possible ways. The one is, as the code below, to use the static member.
<ComboBox ItemsSource="{Binding Source={x:Static local:MainWindow.MicModeOptions} , Mode=OneWay}"/>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public **static** CfgData.TMicMode[] MicModeOptions
{
}
}
The other is to use Resources in XAML, where the target class(MainWindow in your code) has to get a default constructor(parameterless).
<Grid>
<Grid.Resources>
<local:MainWindow x:Key="mainWindow"/>
</Grid.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource mainWindow}, Path=MicModeOptions , Mode=OneWay}"/>
</Grid>
How is the dialog window launched? If it is launched via window.ShowDialog() then you could pass the necessary object you need to bind to as a parameter to the constructor of your dialog window. The constructor then assigns it to an internal property to which your XAML code can bind to.
Try this method, easy and clean.
<!-- In user countrol resources -->
<UserControl.Resources>
<CollectionViewSource Source="{Binding Currencies}" x:Key="Currencies"/>
</UserControl.Resources>
<!-- below inside ex. DataGrid -->
<ComboBox ItemsSource="{Binding Source={StaticResource Currencies}}" IsSynchronizedWithCurrentItem="False"
DisplayMemberPath="IsoCode"
SelectedItem="{Binding BaseCurrency}"/>
<!-- IsSynchronizedWithCurrentItem="False" is important, otherwise ComboBoxes will select same item for each child viewmodel -->
reference to blogpost http://kostylizm.blogspot.ru/2014/04/wpf-combobox-itemssource-bind-to-parent.html

How can I bind this View to this ViewModel?

The following code-behind binding works for the SmartFormView user control:
View:
<UserControl x:Class="CodeGenerator.Views.PageItemManageSettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:CodeGenerator.Views"
xmlns:vm="clr-namespace:CodeGenerator.ViewModels"
Background="#ddd">
<Grid Margin="10">
<ScrollViewer DockPanel.Dock="Top">
<StackPanel Margin="10">
<v:SmartFormView/>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
Code-behind:
using System.Windows.Controls;
using CodeGenerator.ViewModels;
namespace CodeGenerator.Views
{
public partial class SmartFormView : UserControl
{
public SmartFormView()
{
InitializeComponent();
DataContext = new SmartFormViewModel("testing");
}
}
}
However, I want to bind the SmartFormView to its SmartFormViewModel in the ViewModel of the calling View, not hard-coded in the code-behind. Yet these two approaches don't bind:
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:SmartFormViewModel}">
<v:SmartFormView/>
</DataTemplate>
</UserControl.Resources>
...
<Grid Margin="10">
<ScrollViewer DockPanel.Dock="Top">
<StackPanel Margin="10">
<TextBlock Text="{Binding Testing}"/>
<v:SmartFormView DataContext="{Binding SmartFormViewModel}"/>
<ContentControl Content="{Binding SmartFormViewModel}"/>
</StackPanel>
</ScrollViewer>
</Grid>
In the ViewModel I have "Testing" and "SmartFormViewModel" defined as ViewModel properties and fill them both (as shown below), but although the Testing property binds fine, the the SmartFormView does not bind to its SmartFormViewModel:
private SmartFormViewModel _smartFormViewModel=;
public SmartFormViewModel SmartFormViewModel
{
get
{
return _smartFormViewModel;
}
set
{
_smartFormViewModel = value;
OnPropertyChanged("SmartFormViewModel");
}
}
private string _testing;
public string Testing
{
get
{
return _testing;
}
set
{
_testing = value;
OnPropertyChanged("Testing");
}
}
public PageItemManageSettingsViewModel(MainViewModel mainViewModel, PageItem pageItem)
: base(mainViewModel, pageItem)
{
SmartFormViewModel SmartFormViewModel = new SmartFormViewModel("manageSettingsMain");
Testing = "test ok";
}
What is the syntax to bind a UserControl in XAML to a specific ViewModel in the calling View's ViewModel?
Could be wrong, but I think you just have a bug in your code.
SmartFormViewModel SmartFormViewModel = new SmartFormViewModel("manageSettingsMain");
Should be:
SmartFormViewModel = new SmartFormViewModel("manageSettingsMain");
ie. Your SmartFormViewModel is never being set. Therefore, the binding you have in your parent view doesn't find it.
Further to this, a better way to do this is just to stick your child VM into the visual tree:
<ContentControl Content="{Binding SmartFormViewModel}"/>
And use a DataTemplate to do the resolution of the view rather than "hard-coding" the view into the, um, parent view.

Categories

Resources