How to modify inner control property by setting container control dependency property? - c#

I am creating a control in WPF which have inside a label and I have created a dependency property in order to modify the label's visibility property.
My problem is that I cant find a way to change my label's visibility property at the same time my dependency property is assigned.
My code is as below:
public static readonly DependencyProperty captionVisibleProperty = DependencyProperty.Register(
"CaptionVisible",
typeof(bool),
typeof(MyCustomControl));
public bool CaptionVisible
{
get
{
return (bool)GetValue(captionVisibleProperty);
}
set
{
SetValue(captionVisibleProperty, value);
ShowCaption();
}
}
private void ShowCaption()
{
if (CaptionVisible)
{
captionLabel.Visibility = System.Windows.Visibility.Visible;
}
else
{
captionLabel.Visibility = System.Windows.Visibility.Collapsed;
}
}
As you can see I have tried to call my ShowCaption() method when my property is set, but nothing happens.
So, what I am supposed to do in order to get it done?
Hope someone can help me. Thank you in advance.

ShowCaption() shouldn't be necessary. Instead, just bind the label's Visibility property to your CaptionVisible property in xaml. It's also best to follow the Model-View-ViewModel design pattern for keeping your code organized. This means putting the logic for controlling your user interface (the View) in separate ViewModel classes, and then assigning ViewModel to that View's DataContext property.
That will make the binding a lot easier. Referencing properties that belong to user interface elements can sometimes be a bit of a hassle in WPF. By contract, WPF's bindings system was specifically designed to make it easy to get at the contents of a user interface element's DataContext.
You'll also need to use the handy-dandy BooleanToVisiblityConverter to make the binding work, since the Visibility property's type isn't bool. I like to put it in in the window's (or control's) resource dictionary for easy access:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Label Visibility="{Binding Path=CaptionVisible,
Converter={StaticResource BooleanToVisibilityConverter}}">
<!-- label content -->
</Label>
As a side note, unless CaptionVisible is going to be the target of a binding, making it a dependency property is overkill. In this binding it's only the source, so just implementing INotifyPropertyChanged would be sufficient:
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private bool _captionVisible;
public bool CaptionVisible
{
get { return _captionVisible; }
set
{
if(_captionVisible != value)
{
_captionVisible = value;
RaisePropertyChanged("CaptionVisible");
}
}
}
}

I have found a solution to this problem:
If you need to link a nested control dependency property to a container control dependency property you can do this:
public static readonly DependencyProperty captionLabelVisibilityProperty = DependencyProperty.Register(
"CaptionVisibility",
typeof(Visibility),
typeof(MyContainerControl),
new FrameworkPropertyMetadata(
VisibilityPropertyChangedCallback));
public Visibility CaptionVisibility
{
get
{ return (Visibility)GetValue(captionLabelVisibilityProperty); }
set
{ SetValue(captionLabelVisibilityProperty, value); }
}
private static void VisibilityPropertyChangedCallback(DependencyObject controlInstance, DependencyPropertyChangedEventArgs args)
{
MyContainerControl myContainerControlInstance = (MyContainerControl)controlInstance;
myContainerControlInstance.myLabel.Visibility = (Visibility)args.NewValue;
}

Related

nested property propertychanged event only fired once in usercontrol in silverlight

There are two Viewmodels, both of them had implemented the INotifyPropertyChanged interface (I have called the OnpropertyChanged("propertyname") in my actual code).
Public Class A{
public B BProperty
{
get
{
return _BProperty;
}
set
{
if (_BProperty != null)
_BProperty.PropertyChanged -= _BProperty_PropertyChanged;
_BProperty = value;
OnPropertyChanged("BProperty");
if (_BProperty != null)
_BProperty.PropertyChanged += _BProperty_PropertyChanged;
}
}
void _BProperty_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "status")
{
OnPropertyChanged("BProperty");
}
}
B _BProperty;
}
Public Class B
{
public int status{get;set;}
}
I also had a userControl:
<MyUserControl ...
... >
<Grid>
</Grid>
</MyUserControl>
And I had a dependencyProperty:
/// <summary>
/// File status
/// </summary>
public int Filestatus
{
get { return (int)GetValue(FilestatusProperty); }
set { SetValue(FilestatusProperty, value); }
}
public static readonly DependencyProperty FilestatusProperty =
DependencyProperty.Register(
"Filestatus",
typeof(int),
typeof(MyUserControl),
new PropertyMetadata(0, OnFilestatusPropertyChanged));
private static void OnFilestatusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl control = (MyUserControl)d;
if (e.NewValue != null)
{
}
}
edit:2015/09/21
Add the get/set methods:
public static readonly DependencyProperty FileStatusProperty = DependencyProperty.RegisterAttached(
"FileStatus", typeof(int), typeof(FileStatusIconControl), new PropertyMetadata(0, PropertyChangedCallback));
public static int GetFileStatus(DependencyObject source)
{
return (int)source.GetValue(FileStatusProperty);
}
public static void SetFileStatus(DependencyObject target, int value)
{
target.SetValue(FileStatusProperty, value);
}
private static void PropertyChangedCallback(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
FileStatusIconControl fsic = dependencyObject as FileStatusIconControl;
if(fsic != null)
wahahahahaha;
}
edit end
I used this userControl in my mainPage like this:
<mainPage ...
...>
<Grid>
<MyUserControl Filestatus={Binding Bproperty.status} />
<TextBox Text={Binding Bproperty.status} />
</Grid>
</mainPage>
The datacontext of mainPage is an instance of Class A.
My question is:
When status is changed, the text of textbox is changed, but the OnFilestatusPropertyChanged method only was called once when Usercontrol is loaded. Why?
Thanks.
I will start by saying that while I was looking into your question I ran into some problems with the code you have provided. I appreciate that somewhere you have some real code which has a problem and you cannot share this code with us so have tried to reproduce the problem in a smaller set of files.
However, if you do do this, please at least verify that the code you have provided runs and exhibits the problem. It is evident that you haven't tried to run your sample code (particularly the XAML) as there are problems with it:
attribute values should be surrounded in double-quotes,
the binding path Bproperty.status should be BProperty.status (first P capitalised).
All these things slow down someone trying to help you. Worse still, when I do find a problem with your code I can't be sure whether it's a genuine problem that your real code also has or whether it's something you introduced putting together your sample code. So all I can do is point out all the problems I find in the hope that one of them is the problem you have in your real code.
Firstly, your TextBox's Text property binding doesn't contain Mode=TwoWay. In WPF, this binding is TwoWay by default, but in Silverlight all bindings are OneWay by default. If you are familiar with WPF, this may be why you omitted Mode=TwoWay.
Secondly, I don't see why you have implemented class B as you have, apparently leaving it up to class A to fire property-changed events on its behalf. This approach doesn't work: when Silverlight updates the value in the status property of a B instance, it does so by calling the status setter of the B instance. Your B class has only an autogenerated property setter, which certainly doesn't fire the PropertyChanged event. Because this event wasn't fired, Silverlight doesn't know that is has some updates to do, and furthermore your A class isn't aware that it has changed either.
I would implement INotifyPropertyChanged in the usual way in class B, by calling OnPropertyChanged in the status setter. I would also remove the BProperty_PropertyChanged event handler in class A as I don't think it does you any favours.

xamarin.forms binding from xaml to property

I am a total newbie with bindings in xaml and I really don't get it sometimes.
I have this in my xaml:
<ActivityIndicator IsRunning="{Binding IsLoading}" IsVisible="{Binding IsLoading}" />
The binding "IsLoading". Where do I declare/set this property?!
My .cs looks like this:
....
public bool IsLoading;
public CardsListXaml ()
{
InitializeComponent ();
IsLoading = true;
....
Bindings are typically resolved from the BindingContext property (in other implementations, this property is called DataContext). This is null by default (at least in other implementations of XAML), thus your view is unable to find the specified properties.
In your case, you must set the BindingContext property to this:
public CardsListXaml()
{
InitializeComponent();
BindingContext = this;
IsLoading = true;
}
However, this alone will not suffice. Your current solution does not implement a mechanism to notify the view of any property changes, so your view would have to implement INotifyPropertyChanged. Instead, I suggest you implement the Model-View-ViewModel pattern, which not only fits beautifully with data binding, but will result in a more maintainable and testable code base:
public class CardsListViewModel : INotifyPropertyChanged
{
private bool isLoading;
public bool IsLoading
{
get
{
return this.isLoading;
}
set
{
this.isLoading = value;
RaisePropertyChanged("IsLoading");
}
}
public CardsListViewModel()
{
IsLoading = true;
}
//the view will register to this event when the DataContext is set
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
And then in your code-behind's constructor:
public CardsListView()
{
InitializeComponent();
BindingContext = new CardsListViewModel();
}
Just to clarify, DataContext cascades down the visual tree, thus the ActivityIndicator control will be able to read to properties specified in the bindings.
Edit: Xamarin.Forms (and Silverlight/WPF etc... sorry, it's been a while!) also provides a SetBinding method (see the Data Binding section).

Dependency Property assigned with value binding does not work

I have a usercontrol with a dependency property.
public sealed partial class PenMenu : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public bool ExpandCollapse
{
get
{
return false;
}
set
{
//code
}
}
public static readonly DependencyProperty ExpandCollapseProperty = DependencyProperty.Register("ExpandCollapse", typeof(bool), typeof(PenMenu), null);
//some more code
}
And I am assigning value in XAML page as:
<Controls:PenMenu x:Name="penMenu" Opened="Menu_Opened"
ExpandCollapse="{Binding PenMenuVisible}" />
But it is not hitting GET-SET part of ExpandCollapse property in the usercontrol.
So I added bool to bool converter just to check what value is being passed with binding like:
<Controls:PenMenu x:Name="penMenu" Opened="Menu_Opened"
ExpandCollapse="{Binding PenMenuVisible, Converter={StaticResource booleanToBooleanConverter}}" />
And with breakpoint in Converter, I see the value being passed is correct.
What is the possible reason it's not assigned to the Dependency Property?
Also in XAML page if I say:
<Controls:PenMenu x:Name="penMenu" Opened="Menu_Opened"
ExpandCollapse="true"/>
then it hits the GET-SET part of ExpandCollapse property in the usercontrol.
I am stuck. This is weird. Please help.
It's frustrating isn't it? First, include a changed event handler. Like this:
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string),
typeof(MyControl), new PropertyMetadata(string.Empty, Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = d as MyControl;
// now, do something
}
Then, please read this article so you see there are more gotchas than just that one: http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html
Best of luck!
The getter and setter of a dependency property are not guaranteed to be run, and in particular the WPF binding engine / XAML processor is documented to bypass these. Have a look on MSDN - the getter/setter should just be a wrapper around GetValue/SetValue on the DependencyProperty itself.
Instead of reacting in the setter of your property, you should add a property changed handler in the original call to DependencyProperty.Register, when you can act on the new value.
(see other questions).

"Bindable Method" with OneWayToSource mode

I need to write a custom control that looks like a TextBox and that contains a method called Refresh() which main purpose will be to clear the Text and to roll back few other values.
The method shall become bindable somehow so that others can bind a property from their ViewModel with it. Hence why I am thinking that inside my custom control I will need an dependency property of type Action.
So far so logical but next problem is the method/dp may no get overriden on control side once users sets a two way binding on it. Basically I always have to deliver the method wrapper as Action to ViewModel and inside ViewModel other users may call it.
How do I do all this? It seems to me that I have to somehow get the binding of the method work like OneWayToSource.
I apologize in case its a duplicate. Futhermore thanks in advance guys.
EDIT: Please no alternative solutions. Those are the requirements and I have to stick to them.
I think that the simplest thing you can do here is to expose a bool property, maybe called IsCleared, and just call your method from that property when it becomes true. Exposing ICommand and/or delegate objects transfers the functionality out of your control, so you can't use those.
#ninjahedgehog, why can't you use a bool 'switch' property? Your requirement says 'so that others can bind a property from their ViewModel with it'... they can bind to a bool property from their view model. In my opinion, it seems to be your only option. As I said earlier, you can't use ICommand and/or delegate objects as that would transfer the functionality out of your control - that would enable other developers to write their own functionality rather than to just call yours.
What you really want is a method on your control that they could call from their view model... but view models shouldn't have any knowledge about the view controls, so you can't do that. The next best thing to that is creating a method that is called when a property is given a certain value. Here you have a few choices.
If you really don't like the bool switch idea, then how about an enum property? Create an enum with specific values like ClearText and whatever other functionality you would like to expose. Then the other developers simply set this property to the relevant instance to instantiate that functionality... I only suggested the bool switch property because it seems as if you only want to expose one piece of functionality.
One last point to note about using the bool switch property... as it is a switch, you need to reset it after use, or just never actually set it:
public bool IsTextClear
{
get { if (value) ClearText(); }
}
I dont know why you need this coz the person who is using your control can directly call the method from the code behind. But if you want that there should be some property like ClearMe on control and when set to true it should clear the control then you can define the dependency property and listen to its change in control like below and call Refresh from there.
public static readonly DependencyProperty ClearMeProperty = DependencyProperty.Register
(
"ClearMe",
typeof(bool),
typeof(MyControl),
new FrameworkPropertyMetadata(false, OnClearMeChanged)
);
public bool ClearMe
{
get { return (bool)GetValue(ClearMeProperty); }
set { SetValue(ClearMeProperty, value); }
}
private static void OnClearMeChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as MyControl;
if((bool)e.NewValue)
{
control.Refresh()
}
}
and you can bind this property to your ViewModel property. whenever ViewModel property will change to true. Property Change will be fired in control and will refersh it.
I editted my answer, as I wasn't understanding what you wanted. The only way I could come up with to do what you want was to use an Action DependencyProperty on the CustomControl and bind that to the ViewModel using a OneWayToSource binding, that way the Action from the control gets sent to the viewmodel. Within your customcontrol, you can test to make sure that only OneWayToSource binding is used and do something if not.. in this case, I add some text and made the background red.
View
<UserControl x:Class="WpfApplication1.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:controls="clr-namespace:WpfApplication1.Controls">
<UserControl.Resources>
<vm:TestViewModel x:Key="TestViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TestViewModel}">
<StackPanel Orientation="Horizontal" Height="30">
<controls:CustomTextBox Width="300" Refresh="{Binding RefreshAction, Mode=OneWayToSource}" />
<Button Content="Refresh" Width="80" Command="{Binding RefreshFromView}" />
</StackPanel>
</StackPanel>
ViewModel
using System;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
public class TestViewModel : INotifyPropertyChanged
{
public TestViewModel()
{
RefreshFromView = new RelayCommand(ExecuteRefreshFromView);
}
public Action RefreshAction { get; set; }
public RelayCommand RefreshFromView { get; set; }
private void ExecuteRefreshFromView(object parameter)
{
if (RefreshAction != null)
RefreshAction();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyOfPropertyChange(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Custom Control
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfApplication1.Controls
{
public class CustomTextBox : TextBox
{
public CustomTextBox()
{
this.Loaded += CustomTextBox_Loaded;
}
void CustomTextBox_Loaded(object sender, RoutedEventArgs e)
{
BindingExpression bindingExpression = GetBindingExpression(RefreshProperty);
BindingMode mode = bindingExpression.ParentBinding.Mode;
if (mode != BindingMode.OneWayToSource)
{
Text = "Use OneWayToSource Binding only!";
Background = new SolidColorBrush(Colors.Red);
}
Refresh = new Action(DoRefresh);
}
private void DoRefresh()
{
Text = null;
}
public Action Refresh
{
get { return (Action)GetValue(RefreshProperty); }
set { SetValue(RefreshProperty, value); }
}
public static readonly DependencyProperty RefreshProperty = DependencyProperty.Register("Refresh", typeof(Action), typeof(CustomTextBox));
}
}
You could use a Command:
public class Command : ICommand
{
public void Execute(object parameter)
{
// Do whatever you have to do
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
In your ViewModel:
public ICommand Command { get; set; }
In your XAML (assuming that your Custom Control is composed of a TextBox and a Button for example):
<Button Click="{Binding Command}" />

How do I get databound text blocks in a custom control to update?

The setup
So I have a class, ComputerItem, designed to store everything I need to know about a specific computer; these items are stored in an ObservableCollection<ComputerItem>. I then have a custom control ComputerControl, which has (among other things) a few text boxes bound to members of ComputerItem, the bindings made available like so:
<TextBlock Name="tb_computerName"TextWrapping="Wrap" Text="{Binding ElementName=ComputerControl1, Path=computerName}"/>
and in the code behind
public static DependencyProperty computerNameProperty = DependencyProperty.Register("computerName", typeof(string), typeof(ComputerControl), null);
I then create a MultiselectList of ComputerControl objects:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<toolkit:MultiselectList x:Name="lb_computers" IsSelectionEnabledChanged="lb_computers_IsSelectionEnabledChanged"> <!--SelectionChanged="lb_computers_SelectionChanged" >-->
<toolkit:MultiselectList.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="sp">
<local:ComputerControl computerName="{Binding Name}" MACAddress="{Binding DisplayMAC}" playClicked="playClicked_Handler" editClicked="editClicked_Handler"/>
</StackPanel>
</DataTemplate>
</toolkit:MultiselectList.ItemTemplate>
</toolkit:MultiselectList>
</Grid>
and you can see the data bindings in the ComputerControl definition. In the code behind I bind the ObservableCollection to the MultiselectList:
this.lb_computers.ItemsSource = ComputerListMaintainer.GetList();
and all of this (as well as a few things I'm sure I've forgotten to include here) works beautifully to fill the MultiselectList with ComputerControls representing the ComputerItems in the ObservableCollection.
The problem
My issue is that when the underlying ComputerItem changes, the TextBlocks in the corresponding ComputerControl don't update. I've implemented INotifyPropertyChanged in the ComputerItem class:
public class ComputerItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
protected virtual void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public string Name
{
get { return name; }
set { OnPropertyChanged("Name"); name = value; }
}
}
but that didn't solve the problem. I suspect it's something to do with ComputerControl but I have no idea where to start looking; the closest question I've found suggested INotifyPropertyChanged should have been the solution but they weren't using a custom control in that case, just a custom class, if I remember correctly.
What am I missing?
Well your setter is incorrect for starters; also do look into MvvmLight, as it provides a great API for this kind of plumbing work.
public string Name
{
get { return name; }
set
{
if(value != name)
{
name = value;
OnPropertyChanged("Name");
}
}
}
Update:
You shouldn't be setting lb_computers.ItemsSource in your code behind, because that's one time operation and not a binding. It is better to bind to an ObservableCollection of observable objects (aka INotifyPropertyChanged).
Also I'm not sure if you're properly declaring your dependency property, so below you can find a proper setup on how to define a 'bindable' property.
And also with XAML, the architecture of your code matters, to have a sane experience. I highly recommend that you utilize the Mvvm pattern. I find MvvmLight and MEFedMVVM to be great aids in my development. This require a bit of work at the beginning, but it'll be far easier to debug future issues and maintain your code.
If these tips don't help, then I'd have to see your full code.
Declaring a Bindable Property
#region ReportName
public string ReportName
{
get { return (string)GetValue(ReportNameProperty); }
set { SetValue(ReportNameProperty, value); }
}
public static readonly DependencyProperty ReportNameProperty = DependencyProperty.Register("ReportName",
typeof(string), typeof(ExportableGridView), new PropertyMetadata("Report", new PropertyChangedCallback(OnReportNameChanged)));
public static void OnReportNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ExportableGridView control = sender as ExportableGridView;
control.titleTextBlock.Text = e.NewValue as string;
}
#endregion ReportName

Categories

Resources