First of all i will try to explain what i am doing. I am trying to draw a chess board. I have a user controll for cell
<Grid x:Name="LayoutRoot">
<Border BorderThickness="0" Margin="0" Background="{Binding CellColor, ElementName=userControl, Mode=TwoWay}"/>
<Border x:Name="ValidMoveMarker" BorderThickness="0" Margin="0" Background="#FFC1CAB4" Opacity="0"/>
<Image x:Name="img" Source="{Binding source, ElementName=userControl, Mode=TwoWay}" Cursor="Hand"/>
In code behind of this CellControl i have 2 dpProperties
public eColor? PieceColor
{
get { return (eColor?)GetValue(PieceColorProperty); }
set { SetValue(PieceColorProperty, value);}
}
public static readonly DependencyProperty PieceColorProperty = DependencyProperty.Register("PieceColor", typeof(eColor?), typeof(CellControl), null);
public eType? PieceType
{
get { return (eType?)GetValue(PieceTypeProperty); }
set { SetValue(PieceTypeProperty, value);}
}
public static readonly DependencyProperty PieceTypeProperty = DependencyProperty.Register("PieceType", typeof(eType?), typeof(CellControl), null);
where eColor and eType are enumerators. Here I also have one property
public ImageSource source
{
get
{
if (PieceColor == eColor.White)
{
switch (PieceType)
{
case eType.Pawn:
return new BitmapImage(new Uri("/PO.PC;component/Images/chess_piece_white_pawn_T.png", UriKind.Relative));
case eType.Knight:
return new BitmapImage(new Uri("/PO.PC;component/Images/chess_piece_white_knight_T.png", UriKind.Relative));
...
default:
return null;
}
}
else
{
switch (PieceType)
{
case eType.Pawn:
}
}
}
Now problem is when i try to use the control like this
<PP_Controls:CellControl PieceType="{Binding type, Mode=TwoWay}" PieceColor="{Binding color, Mode=TwoWay}"
where
private eColor? _color;
public eColor? color
{
get { return _color; }
set
{
_color = value;
OnPropertyChanged("color");
}
}
private eType? _type;
public eType? type
{
get { return _type; }
set
{
_type = value;
OnPropertyChanged("type");
}
}
nothings happens. But if i use control like this
<PP_Controls:CellControl PieceType="Bishop" PieceColor="Black"
it is working perfectly. Am I missing something in my bindings? Is this because "source" property is not dependency property itself? How can I workaround my problem?
Your target properties are dependency properties, and the source properties are CLR properties implementing INotifyPropertyChanged, so your binding {Binding type} etc should work - assuming that the DataContext to your "use with binding" is the type with the color/type properties. You should be able to tell if these bindings are failing by looking at the Output window when you run your application under the debugger (in Silverlight 5 you could also use a binding breakpoint, or otherwise you can apply a trivial ValueConverter to the binding to set a breakpoint for debugging).
However your control's source property depends on two other properties in a 'lazy' fashion. You are binding to the source property, but nothing will cause this binding to update when the property's computed value changes. You should add a dependency property changed handler to PieceColor and PieceType which calls OnPropertyChanged("source") (or equivalently convert it to a DP or notifying property, and explicitly recompute the value).
Related
I defined a custom loading spinner UserControl in a WPF UserContol library.
It has one dependency property:
public string SpinnerSourcePath { get => _spinner.Source.ToString(); set => _spinner.Source = (ImageSource)new ImageSourceConverter().ConvertFromString(value); }
public static readonly DependencyProperty SpinnerSourcePathProperty =
DependencyProperty.Register(nameof(SpinnerSourcePath), typeof(string), typeof(Spinner));
where _spinner is the Image.
(I tried it directly with ImageSource class but no dice)
The xaml looks like this:
<Image x:Name="_spinner" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
and I use it by defining it like:
<c:Spinner SpinnerSourcePath="/Test;component/_Resources/loading.png"/>
(The project name is Test, the Spinner control resides in a different project), nothing is displayed.
However, if I add the Source property directly in the Spinner definition:
<Image x:Name="_spinner" Source="/Test;component/_Resources/loading.png" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
it shows correctly...
This leads me to believe that the dependency property is wrong, but how ?
E1:
After trying to do the same steps on a different control it stopped working again.
This time I have a DP:
public static readonly DependencyProperty ValidationFunctionProperty =
DependencyProperty.Register(nameof(ValidationFunction), typeof(Func<string, bool>), typeof(ValidatedTextBox), new PropertyMetadata(OnAssignValidation));
public Func<string, bool> ValidationFunction {
get => (Func<string, bool>)GetValue(ValidationFunctionProperty);
set => SetValue(ValidationFunctionProperty, value);
}
private static void OnAssignValidation(DependencyObject d, DependencyPropertyChangedEventArgs e) {
Debugger.Break();
}
Control usage:
<c:ValidatedTextBox x:Name="valid"
Text="Test"
ValidationFunction="{Binding Validation, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource test}}"/>
The converter is just a Debugger.Break() and return original
And finally the RelativeSource control is my MainWindow
public MainWindow() {
InitializeComponent();
}
public Func<string,bool> Validation => (s) => true;
(There is a problem with the Text DP as well, but I think I can solve that one on my own)
E2
Ok Pro problem was the RelativePath pointing to UserControl but it was placed in a Window
Your dependency property declaration is wrong, because the get/set methods of the CLR property wrapper must call the GetValue and SetValue methods of the DependencyObject base class (and nothing else).
Besides that, the property should also use ImageSource as its type:
public static readonly DependencyProperty SpinnerSourceProperty =
DependencyProperty.Register(
nameof(SpinnerSource), typeof(ImageSource), typeof(Spinner));
public ImageSource SpinnerSource
{
get { return (ImageSource)GetValue(SpinnerSourceProperty); }
set { SetValue(SpinnerSourceProperty, value); }
}
The Image element in the UserControl's XAML would use the property like this:
<Image Source="{Binding SpinnerSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
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...
Qeustion:
Is it possible to add an additional PropertyDescriptor to the PropertyDescriptorCollection of an ICustomTypeDescriptor after DataConext has been set?
Details:
I am creating a new class which implements ICustomTypeDescriptor with the goal of dynamically adding new Properties to this class at runtime, and binding them to a WPF application.
For example, a user may define a new property through a scripting language (eg. Lua) while the app is running, and I would like XAML to be able to Bind to that Property.
The issue I am running into is that if I add a property AFTER the DataContext has been set, the Binding to XAML does not work; it does not attempt to call associated PropertyDescriptor.GetValue method.
I believe this issue is that shortly after setting the DataContext to my ICustomTypeDescriptor, its implementation of ICustomTypeDescriptor.GetProperties is called, at which point I create PropertyDescriptor for all known Properties. I then later add new Properties, but don't have the ability to add new PropertyDescriptor for them.
Code:
I have trimmed down the code a bit here to make it easier to read, removing a lot of the boiler-plate stuff.
The idea in this example is that the first text field is Bound to a dynamically created property, which is defined during construction, and the second text field is Bound to a property that won't exist until the Button is clicked.
The property created when the button is clicked only gets DataBound properly if I set the DataContext to something new.
public class MyCustomType : ICustomTypeDescriptor, INotifyPropertyChanged
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
public event PropertyChangedEventHandler PropertyChanged;
public MyCustomType()
{
Properties.Add("CustomProp", "What up, world?");
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public void CreateOrSetProperty(string name, object val)
{
Properties[name] = val;
NotifyPropertyChanged(name);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<PropertyDescriptor> props = new List<PropertyDescriptor>();
foreach (KeyValuePair<string, object> entry in Properties)
{
props.Add(new MyCustomTypePropertyDescriptor(entry.Key, attributes));
}
return new PropertyDescriptorCollection(props.ToArray());
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
class MyCustomTypePropertyDescriptor : PropertyDescriptor
{
public MyCustomTypePropertyDescriptor(string name, Attribute[] attrs)
: base(name, attrs)
{
}
public override object GetValue(object component)
{
return ((MyCustomType)component).Properties[Name];
}
public override bool IsReadOnly
{
get { return false; }
}
public override void SetValue(object component, object value)
{
((MyCustomType)component).Properties[Name] = value;
}
}
}
public partial class Page5
{
public Page5()
{
this.InitializeComponent();
}
private void BtnAddProperty_Click(object sender, System.Windows.RoutedEventArgs e)
{
// A static resource defined in XAML used as the DataContext for both text blocks.
MyCustomType c = Resources["MyCustomTypeData"] as MyCustomType;
// Create a new property which is already being referenced in a DataBind in XAML but
// prior to this function being called, did not exist.
c.CreateOrSetProperty("AnotherProp", "Another Property!");
// If I clear the DataContext and set it back to the previous value it triggers
// ICustomTypeDescriptor.GetProperties to get called again, and the property gets
// Bound. If I don't do this, it fails.
//MyTextBlock2.DataContext = null;
//MyTextBlock2.DataContext = c;
}
}
<Page.Resources>
<WpfApplication1:MyCustomType x:Key="MyCustomTypeData"/>
</Page.Resources>
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock DataContext="{StaticResource MyCustomTypeData}" x:Name="MyTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding CustomProp}" VerticalAlignment="Top" FontSize="32" Background="Black" Foreground="White"/>
<TextBlock DataContext="{StaticResource MyCustomTypeData}" x:Name="MyTextBlock2" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding AnotherProp}" VerticalAlignment="Top" FontSize="32" Background="Black" Foreground="White"/>
<Button x:Name="BtnAddProperty" Content="Add Additional Property" Click="BtnAddProperty_Click"/>
</StackPanel>
</Grid>
I don't fully understand your question. However, I guess it maybe part of your problem that you use a Dictionary in a binding expression. In my opinion the collection which will be bound in xaml should be a type derrived from ObservableCollection in order to own the ability to notify view when collection change.
I have checked StackOverflow looking for the answer, and asking here is my last resort, so please do not downvote. I need an opinion of someone more experienced in databinding than me:
Here is what I want to bind:
<Grid x:Name="all" Grid.Row="1">
<TextBlock Text="all" Foreground="{Binding Color}" x:Name="allTxt" Grid.Row="0" Padding="10,21,0,0" FontWeight="Light" FontSize="{StaticResource PhoneFontSizeExtraLarge}" Tap="TextBlock_Tap_1" />
</Grid>
And this is how I set DataContext of the Grid component
public Multiplication()
{
InitializeComponent();
this.all.DataContext = App.MyObjectsViewModel.allObjectsVisible;
}
Here is the constructor for MyObjectsViewModel
public MyObjectsViewModel(DataContextManager manager)
{
allObjectsVisible = new allVisibleBtn() { Allvisible = true, Color = "white" };
}
And here is the allObjectsVisible attribute:
private allVisibleBtn _allObjectsVisible;
public allVisibleBtn allObjectsVisible
{
get { return _allObjectsVisible;}
set { _allObjectsVisible= value; NotifyPropertyChanged("allObjectsVisible"); }
}
And finally here is the class for allObjectsBtn:
public class allVisibleBtn : BaseViewModel
{
private bool _Allvisible;
public bool Allvisible
{ get { return _Allvisible; }
set { NotifyPropertyChanging("Allvisible"); _Allvisible = value; NotifyPropertyChanged("Allvisible"); }
}
private string _Color;
public string Color
{
get { return _Color; }
set { if (value != null) { NotifyPropertyChanging("Color"); _Color = value; } NotifyPropertyChanged("Color"); }
}
}
The expected behavior is changing textblock foreground the moment someone taps it. OFC there are some more things that are done after the tap, but nothing significant from the point of the problem.
So please help me out, why do i get the following error:
`System.Windows.Data Error: BindingExpression path error: 'Color' property not found on 'Project.MyObjectsViewModel' 'Project.MyObjectsViewModel' (HashCode=50930930). BindingExpression: Path='Color' DataItem='Project.MyObjectsViewModel' (HashCode=50930930); target element is 'System.Windows.Controls.TextBlock' (Name='allTxt'); target property is 'Foreground' (type 'System.Windows.Media.Brush')..`
The DataContext of the TextBlock seems to be an instance of Project.MyObjectsViewModel, and that type does not have a Color property. So you need to set the DataContext of your TextBlock to an instance of allVisibleBtn. Try
<TextBlock DataContext="{Binding Path=allObjectsVisible}" ... />
Once you get that working, you would need to change the type of the Color property on allVisibleBtn. The Foreground property of the TextBlock expects a Brush, not a string.
I'm using template selector for data grid in WPF.
I've got this code:
<l:ProblemTemplateSelector x:Key="problemTemplateSelector">
<l:ProblemTemplateSelector.ArithmeticTemplate>
<DataTemplate>
<Grid Background="LightBlue">
<l:ArithmeticUserControl Problem="{Binding ElementName=this}" />
</Grid>
</DataTemplate>
</l:ProblemTemplateSelector.ArithmeticTemplate>
</l:ProblemTemplateSelector>
In the usercontrol, I need to set in Problem property all the element, but I don't know what I'm doing wrong. Thanks.
UPDATE 1:
I'm gonna be more specific if you need details, I've got a class Exercise where is a user control:
public class Exercise : UserControl
{
public static readonly DependencyProperty ProblemProperty = DependencyProperty.Register(
"Problem", typeof(Problem), typeof(Exercise), new PropertyMetadata(null, ));
public virtual Problem Problem
{
get
{
return (Problem)GetValue(ProblemProperty);
}
set
{
SetValue(ProblemProperty, value);
}
}
}
This I've got another class where is derived from Exercise.
public partial class ArithmeticUserControl : Exercise
{
public ArithmeticUserControl()
{
InitializeComponent();
}
public override Problem Problem
{
get
{
return base.Problem;
}
set
{
base.Problem = value;
Arithmetic p = (Arithmetic)value;
Number1 = p.Number1;
Number2 = p.Number2;
Operator = p.Operation;
}
}
}
You should just be able to declare the binding as "{Binding}" and have it bind the Problem property to the data item.
EDIT: After seeing your example, you need to change how you're handling the Problem property. The WPF binding system does not make use of CLR properties, so the CLR properties exist only as a convenience for user code. Because of this, the code in your property is never being executed when the value is set through binding.
Instead, you need to handle property value changes by overriding OnPropertyChanged. For example, in your inherited class:
protected override OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(e.Property == ProblemProperty)
{
Arithmetic p = (Arithmetic)Problem;
Number1 = p.Number1;
Number2 = p.Number2;
Operator = p.Operation;
}
}
Simply use a {Binding} to pass a Data Item in the control.
If this won't help, along with your control add a TextBlock and see what is binds through straight binding:
<Grid Background="LightBlue">
<l:ArithmeticUserControl Problem="{Binding ElementName=this}" />
<TextBlock Text="{Binding}" Background="Green" ></TextBlock>
</Grid>
So on green background you shoudl see what is came to the ListBoxItem