Binding Items in Tab Control ItemSource to a User Controls DependencyProperty [duplicate] - c#

This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
How to correctly bind to a dependency property of a usercontrol in a MVVM framework
(4 answers)
Closed 5 years ago.
I have a tab control that is created from a list of objects and a WPF User control that I want to associate directly with one of these objects. I am creating this association as an Dependency Property in the control.
The binding error I get is:
System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'WPFTester.Control' and 'WPFTester.TestClass'. Consider using Converter property of Binding. BindingExpression:Path=; DataItem='Control' (Name=''); target element is 'Control' (Name=''); target property is 'ClassDependency' (type 'TestClass')
It seems as though it changes its mind what the datacontext is before it binds.
The Main Window Creates the list of Objects and attaches them to a tab control. The Issue I am having regards the binding to the Control Dependency Object here.
XAML:
<Grid>
<TabControl ItemsSource="{Binding Classes}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<local:Control ClassDependency="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Code-Behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private BindingList<TestClass> classes;
public event PropertyChangedEventHandler PropertyChanged;
public BindingList<TestClass> Classes
{
get { return classes; }
set
{
classes = value;
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs("Classes"));
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Classes = new BindingList<TestClass>
{
new TestClass() { Title = "123", Content="abc" },
new TestClass() { Title = "456", Content="def" }
};
}
The rest of the classes are simple and I don't think the issue is in them but I will provide them anyways.
The Data Class is simple:
public class TestClass
{
public string Title { get; set; }
public string Content { get; set; }
}
The User control displays a bound value and a constant value. It also presents a Dependency Property.
XAML:
<StackPanel>
<TextBlock Text="{Binding TextDependency}" Margin="5"/>
<TextBlock Text="I'm here"/>
</StackPanel>
Code-Behind:
public partial class Control : UserControl
{
public static readonly DependencyProperty ClassDependencyProperty =
DependencyProperty.RegisterAttached(
"ClassDependency",
typeof(TestClass),
typeof(Control));
public TestClass ClassDependency
{
get { return (TestClass)GetValue(ClassDependencyProperty); }
set { SetValue(ClassDependencyProperty, value); }
}
public Control()
{
InitializeComponent();
this.DataContext = this;
}
}

Related

How do you bind an observable collection to a listview located in a modal/window?

I have a wpf application that I want to be able to launch a separate window in which I will have a listview bound to an observable collection. However I am unable to get the collection values to appear in the list view. Here is some of the code.
Window (Named WizardView):
(Data context defined like so at top of xaml):
d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}"
<Border Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" BorderBrush="Black">
<ListView BorderThickness="0" ItemsSource="{Binding TestModel.FailedTests}">
<Label Content="Introduction" FontWeight="Bold" />
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding }"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
MainViewModel Code:
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
TestModel = new TestViewModel();
WizardModel = new WizardViewModel(TestModel);
}
private WizardViewModel _wizardModel;
public WizardViewModel WizardModel
{
get
{
return _wizardModel;
}
set
{
_wizardModel = value;
RaisePropertyChanged();
}
}
private TestViewModel _testViewModel;
public TestViewModel TestModel
{
get
{
return _testViewModel;
}
set
{
_testViewModel = value;
RaisePropertyChanged();
}
}
WizardViewModel Code:
public class WizardViewModel : TestViewModel
{
internal TestViewModel TestModel;
public WizardViewModel(TestViewModel testModel)
{
TestModel = testModel;
(TroubleShootCommand is defined in seperate UC, and launches fine)
TestModel.TroubleShootCommand = new DelegateCommand(Load, CanTroubleShoot);
}
public void Load()
{
(Sync Root is used because it is running on worker thread. Issue somewhere here?)
_syncRoot.Send(o =>
{
var troubleShootWizard = new WizardView();
troubleShootWizard.Owner = Application.Current.MainWindow;
troubleShootWizard.ShowDialog();
}, null);
}
Observable Collection in TestViewModel (Initialized in ctor):
private ObservableCollection<string> _failedTests;
public ObservableCollection<string> FailedTests
{
get { return _failedTests; }
set
{
_failedTests = value;
RaisePropertyChanged();
}
}
Any Help is appreciated, I feel like I have tried everything. I have watched values through the watch window under TestModel.FailedTests in the collection right before and right after launch.
First,
(Data context defined like so at top of xaml): d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}"
This is a mistake, this way d: you are defining the DataContext at design time..
You can create the viewmodel inside .xaml this way:
<WizardView.DataContext>
<viewModels:MainViewModel/>
</WizardView.DataContext>
Using the design time declaration can help in many ways like knowing the viewmodel in case you are creating it and assigning it in C# (or via a IoC mechanism), also it helps tools like IntelliSense and ReSharper to analyze your bindings and warn you if you misspell a property's name in xaml, auto-completion, etc... (more on this can be found here, and here)
Second, if you are assigning the WizardViewModel in your .xaml the same way (i.e. design-time), then you can either do it in your Load() function (add troubleShootWizard.DataContext = this;) or assign it in .xaml the same way I've mentioned before.

Binding model with multiple properties in UserControl using one DependencyProperty [duplicate]

This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Closed 4 years ago.
I would like to be able to bind complex model (many properties) to UserControl through DependencyProperty, and if model would be edited in UserControl I would like to see this edited information inside my binded model.
Example application: Model, UserControl (xaml + cs), MainWindow (xaml + cs). I have no ViewModel to simplify idea.
Model:
public class MyModel : INotifyPropertyChanged
{
private string _surname;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Surname
{
get => _surname;
set
{
_surname = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MyModelEditor.xaml (inside Grid):
<DockPanel>
<TextBox Text="{Binding MyModel.Name}"/>
<TextBox Text="{Binding MyModel.Surname}"/>
</DockPanel>
Also contains this line in UserControl root element:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
MyModelEditor.xaml.cs:
public partial class MyModelEditor : UserControl
{
public MyModel MyModel
{
get => (MyModel)GetValue(MyModelProperty);
set => SetValue(MyModelProperty, value);
}
public static readonly DependencyProperty MyModelProperty =
DependencyProperty.Register("MyModel", typeof(MyModel), typeof(MyModelEditor), new FrameworkPropertyMetadata(null));
public MyModelEditor()
{
InitializeComponent();
}
}
MainWindow.xaml (inside Grid):
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Press Me!" Click="ButtonBase_OnClick"/>
<controls:MyModelEditor MyModel="{Binding MyModel}"/>
</DockPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private MyModel _myModel;
public MyModel MyModel
{
get => _myModel;
set
{
_myModel = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(MyModel?.Name);
}
}
My test scenario: type text in textbox, press button.
Current behavior: Message after pressing button is empty.
Expected behavior: Message after pressing button is same like in textbox.
I wold not like to bind to all properties separately, because in future I will have much more then two properties.
Why current approach does not work?
How can I achieve my goal?
You are apparently not using the UserControl instance as Binding source in your UserControl's XAML. One way to do this would be to set the Binding's RelativeSource:
<TextBox Text="{Binding MyModel.Name,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
However, you don't need a new dependency property at all for this purpose. Just bind the UserControl's DataContext to a MyModel instance, like
<controls:MyModelEditor DataContext="{Binding MyModel}"/>
The Bindings in the UserControl's XAML would automatically work with the MyModel object, like this:
<DockPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Surname}"/>
</DockPanel>
For both of your TextBox controls, you should define their Binding with a TwoWay mode (ms docs on binding modes). Which, basically, would assure that the data flow is working in both direction (i.e. from the view model into the view and the other way around):
<DockPanel>
<TextBox Text="{Binding MyModel.Name, Mode=TwoWay}"/>
<TextBox Text="{Binding MyModel.Surname, Mode=TwoWay}"/>
</DockPanel>
As a good practice, you should always explicitly define what is the mode of the the Binding (NOTE: by default it's OneWay TwoWay - how to know which is the default?).
Another tip would be to go ahead and use MvvmHelpers nuget (github project), which could spare you the time of implementing INotifyPropertyChanged. Besides, you shouldn't re-invent the wheel
EDIT: Fixes are in your GitHub repo
Two things to note here
You have not instantiated your ViewModel (i.e. MyModel), so it was always null
You don't need to create DependencyPropery every time you want to pass some information to your UserControl. You could simply bind the DataContext itself

Passing ModelData (context) between UserControls (views) MVVM Prism

I'm woking on a project and I have three ViewModels: ObjectDetailsViewMode has a Context (property linking to a model) of type ObjectBase; PropertyTextViewModel has a Context of type PropertyText and PropertyNumberViewModel has a Context of type PropertyNumber.
Below is the structure of the Models:
public class ObjectBase : ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public DataCollection<PropertyBase> Properties { get; } = new DataCollection<PropertyBase>();
}
public class PropertyText : PropertyBase
{
private string _default;
public string Default
{
get { return _default; }
set { SetProperty(ref _default, value); }
}
}
public class PropertyNumber : PropertyBase
{
private double _default = 0;
public double Default
{
get { return _default; }
set { SetProperty(ref _default, value); }
}
private double _minValue = 0;
public double MinValue
{
get { return _minValue; }
set { SetProperty(ref _minValue, value); }
}
private double _maxValue = 0;
public double MaxValue
{
get { return _maxValue; }
set { SetProperty(ref _maxValue, value); }
}
}
Regarding the views I have one for each ViewModel. The ObjectDetailsView is a use control that has a TextBox for editing the Object.Name, two buttons to add new PropertyText/PropertyNumber to the Object.Properties and an ItemsControl connected to that Object.Properties.
Each PropertyBase in the ItemsControl (ItemsSource) is resolved into a new view using the DataTemplate marker:
<ItemsControl ItemsSource="{Binding Object.Properties}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type models:PropertyText}">
<views:PropertyTextView />
</DataTemplate>
<DataTemplate DataType="{x:Type models:PropertyNumber}">
<views:PropertyNumberView />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
As I'm using PRISM the correct ViewModel is automatically created for me and the view DataContext is then set to the new ViewModel. My problem is I need to pass the new Property from the Object.Properties list to the newly created View's ViewModel and store it in the Context property I have there.
I can't avoid creating a View/ViewModel for each property type because there is some under-the-hood logic on some Property types (not the ones I described here.. but I have other types like Boolean, Reference, Enum...)
So I really need to pass a value to the ViewModel I tried to use
<ItemsControl ItemsSource="{Binding Object.Properties}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type models:PropertyText}">
<views:PropertyTextView Context="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type models:PropertyNumber}">
<views:PropertyNumberView Context="{Binding}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
Be aware that Context is a custom property I created inside the ViewModel's to store the ModelContext. I even created a DependencyProperty in the View's behind code:
public PropertyBase Context
{
get { return (PropertyBase)GetValue(ContextProperty); }
set { SetValue(ContextProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContextProperty =
DependencyProperty.Register("Context", typeof(PropertyBase), typeof(PropertyTextView), new PropertyMetadata(null));
But it doesn't get linked to the ViewModels set event (I made a break point there and... nothing). I even tried a SetBinding in the PropertyTextView code-behind (constructor):
string propertyInViewModel = "Context";
var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
this.SetBinding(ContextProperty, bindingViewMode);
No luck with any of these... I' really stuck.
Something More Simple
If the PropertyTextView has this dependency property.
public string Context
{
get { return (PropertyBase)GetValue(ContextProperty); }
set { SetValue(ContextProperty, value); }
}
// Using a DependencyProperty as the backing store for Context. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContextProperty =
DependencyProperty.Register("Context", typeof(string), typeof(PropertyTextBuilderView), new PropertyMetadata(null));
I should be able to do:
right?! Why isn't the public property "Context" not being called (I placed a breakpoint there and I get nothing).
Instead of just setting the Context Property of your View to a new Binding you need to assign the Current DataContext like so:
<views:PropertyNumberView Context="{Binding .}"/>
This should assign the Current Views.DataContext Property to your new View.
If you're in an DataTemplate you probably need to specify the RelativeSource:
<views:PropertyNumberView Context="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}
<ItemsControl ItemsSource="{Binding Object.Properties}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type models:PropertyText}">
<views:PropertyTextView Context="{Binding .}"/>
</DataTemplate>
<ItemsControl.Resources>
</ItemsControl>
As I'm using PRISM the correct ViewModel is automatically created for me
You don't have to use view-first with Prism. The ViewModelLocator is there to help, if you chose to, but view model-first is possible, too.
If I understand you correctly, you have a view model and want to populate a list with child view models. So do just that:
internal class ParentViewModel : BindableBase
{
public ParentViewModel( ParentModel parentModel, IChildViewModelFactory factory )
{
Children = new object[] { factory.CreateTextViewModel(parentModel.TextProperty), factory.CreateNumberViewModel(parentModel.NumberProperty) };
}
public IEnumerable Children { get; }
}
and map the different child view models to child views via DataTemplates.
parentModel.WhateverProperty will have a Name and Value properties as well as setter for the value, probably...

Automatic updte of ListView items

I am new to WPF Binding. Is there any way the listview automatically update when one of the item in ItemSource modifies its own dependecny property. I was trying it to do with FreezableCollection.
My code is given below and the aim is to update the listbox when the textbox is modified.
MainWindow.xaml
<Grid x:Name="mainDataGrid">
<StackPanel Orientation="Horizontal">
<ListView x:Name="membersListView" ItemsSource="{Binding}" MinWidth="100"/>
<StackPanel>
<TextBox x:Name="selectedItemTextBox" Text="{Binding ElementName=membersListView, Path=SelectedItem.Name, Mode=TwoWay}" MinWidth="200"/>
</StackPanel>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
vm.Add(new Model() { Name = "Name1" });
vm.Add(new Model() { Name = "Name2" });
this.DataContext = vm;
}
}
public class Model : Freezable
{
public String Name
{
get { return (String)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public override string ToString()
{
return Name;
}
// Using a DependencyProperty as the backing store for Name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(String), typeof(Model), new PropertyMetadata(""));
protected override Freezable CreateInstanceCore()
{
return new Model();
}
}
public class ViewModel : FreezableCollection<Model>
{
}
Ok,
Right now your ListView is showing the String Representation of your models, That's why you had to override the "ToString()" method... because you couldn't get it to understand to show the Name property.
Now what happens is that your TextBox changes the Name property well but your listbox doesn't know that "Name" property has changed... because it's looking at ToString()
if you set the "DisplayMemberPath" of your ListView to "Name" , it will not look at ToString(), but rather "Name"... like this:
<ListView x:Name="membersListView" ItemsSource="{Binding}" DisplayMemberPath="Name" MinWidth="100"/>
Note that in this mode if you change the Name property using textbox, the textbox won't update the value of "Name" Property instantly until it loses focus, so to fix that change the binding of textbox text to this:
<TextBox x:Name="selectedItemTextBox" Text="{Binding ElementName=membersListView, Path=SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="200"/>
I've added "UpdateSourceTrigger=PropertyChanged" to ensure that as you start changing the text of TextBox, the Name property is updated instantly.
:) hope it helps.

Use a binding to set the text property of a textbox in a usercontrol - WPF

I have a user control which contains a textbox and have created a get/set in the usercontrol to get/set the text property of the textbox.
public class OpenFileControl : UserControl
{
StackPanel sp;
public TextBox tb;
public string Text { get { return tb.Text; } set { tb.Text = value; } }
I then want to set this value based on a binding later on -
<gX3UserControls:OpenFileControl Text="{Binding Value}" />
But I get the following exception
A 'Binding' cannot be set on the 'Text' property of type 'OpenFileControl'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
After some investigation It seems Text needs to be a dependency property, but If I do that I cant work out how to pass the value on to the textbox.
How can I fix this.
Consider using something like this.
Control XAML:
<UserControl x:Class="WpfTestBench.OpenFileControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},
Path=Filename, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</UserControl>
Control codebehind:
using System.Windows;
namespace WpfTestBench
{
public partial class OpenFileControl
{
public static readonly DependencyProperty FilenameProperty =
DependencyProperty.Register("Filename", typeof (string), typeof (OpenFileControl));
public OpenFileControl()
{
InitializeComponent();
}
public string Filename
{
get { return (string)GetValue(FilenameProperty); }
set { SetValue(FilenameProperty, value); }
}
}
}
Main XAML:
<Window x:Class="WpfTestBench.OpenFileWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfTestBench="clr-namespace:WpfTestBench"
Title="OpenFileWindow" Width="300" SizeToContent="Height">
<StackPanel>
<wpfTestBench:OpenFileControl x:Name="In" Filename="{Binding SelectedFilename, UpdateSourceTrigger=PropertyChanged}" />
<wpfTestBench:OpenFileControl x:Name="Out" Filename="{Binding ElementName=In, Path=Filename}" />
</StackPanel>
</Window>
Main codebehind:
namespace WpfTestBench
{
public partial class OpenFileWindow
{
public OpenFileWindow()
{
InitializeComponent();
DataContext = this;
}
public string SelectedFilename { get; set; }
}
}
Execution result (after typing something in the first control):
If you define the dependency property as the static and the actual property, you can write whatever code behind you want in the body of the property.
public const string TextPropertyName = "Text";
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
TextPropertyName,
typeof(string),
typeof(MyControl),
new UIPropertyMetadata(false));
In the getter and setter you can do something like textBox1.Text = value; but you'd probably be better served using a binding to the property instead. MVVM frameworks make light work of this sort of thing quite often. You might find more success defining a ViewModel (a class with an appropriate FielPath variable for example) and setting the DataContext of the new UserControl to be an instance of the ViewModel class, using Bindings to do the heavy lifting for you.

Categories