I have a control that has a label on it, that I would like to hide or show based on a global menu item for all instances of my control. If I click the button to hide labels, I want to to hide all of them.
My xaml looks like this:
<TextBlock Name="_label" Visibility="{Binding LabelShown}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
in my code behind I have a property:
private static Visibility _labelShown;
public static Visibility LabelShown
{
get { return _labelShown; }
set { _labelShown = value; }
}
And I set DataContext = this;
When I change the static property, nothing happens. I assume this is because no controls are getting a property changed notification. I cannot implement INotifyPropertyChanged on it, because I cannot reference the non static property changed handler from my static property.
I feel like maybe this isn't the best way to do this, but I would really like to have one button (many levels above my actual control) drive the visibility for all instances.
CodeNaked's solution works, but it uses a Singleton which has downsides when doing unit-testing. I prefer to approach global access problems by just having one settings instance at the application root, i.e. the App-class.
e.g.
public partial class App : Application
{
private static Settings _settings = new Settings();
public static Settings Settings
{
get { return _settings; }
}
...
Where this property contains all the settings for the application. Binding then looks like this:
"{Binding Source={x:Static local:App.Settings}, Path=LabelsShown}"
Edit: If you are worried about dependencies you could also inject a reference to those settings in the constructor of any class where you need it, using its minimal interface.
e.g.
public class MyControl : ContentControl
{
public interface IMyControlSettings
{
public bool LabelsShown { get; set; }
}
private IMyControlSettings _settings;
public MyControl(IMyControlSettings settings)
{
_settings = settings;
DataContext = _settings; // For easy binding, in most cases you probably do not want to do that since it prevents DataContext inheritance.
}
}
public class Settings : Test.MyControl.IMyControlSettings, INotifyPropertyChanged
{
public bool LabelsShown { get; set; }
...
}
You can do something like this:
public class MySettings : INotifyPropertyChanged {
private MySettings() {
}
private Visibility _labelShown;
public Visibility LabelShown
{
get { return _labelShown; }
set {
_labelShown = value;
// Raise PropertyChanged event for LabelShown
}
}
private static MySettings _instance;
public static MySettings Instance
{
get {
if (_instance == null)
_instance = new MySettings();
return _instance;
}
}
}
Then bind to it like {Binding Path=LabelShown, Source={x:Static local:MySettings.Instance}}
The only thing you need to add is the local xmlns which would be like xmlns:local="clr-namespace:MyNamespace"
I found a kinda lame workaround:
public static Visibility LabelShown
{
get { return _labelShown; }
set
{
_labelShown = value;
if ( StaticEvent != null )
{
StaticEvent();
}
}
}
private static event Action StaticEvent;
public event PropertyChangedEventHandler PropertyChanged
{
add { StaticEvent += () => value(this, new PropertyChangedEventArgs("LabelShown")); }
remove { StaticEvent -= () => value(this, new PropertyChangedEventArgs("LabelShown")); }
}
It works, but I am a little worried about the remove handler actually being able to remove the anonymous method like that. Would that cause memory problems if many controls are disposed?
I tend to prefer CodeNaked's solution, but I wanted to offer this for discussion.
Related
I currently have a simple WPF application, in the MainWindow I will have a variable (In this case the variable is a class that holds data). Then I have a User Control which has the same variable.
Currently, I'm passing the variable with the ref keyword and it works perfectly fine, however, is this save/good practice? Is there a better way of linking this two variables together?
I am aware of the existence of DependencyProperty, however, I could not get it to work.
MainWindow:
public partial class MainWindow : Window
{
private TestClassWithInfo m_SelectedInfo;
public MainWindow()
{
InitializeComponent();
m_SelectedInfo = new DrawingInformation();
TestGridUC mp = new TestGridUC(ref m_SelectedInfo);
TestCanvas.Childrens.Add(mp);
}
}
TestGridUI:
public partial class TestGridUC : UserControl {
private TestClassWithInfo m_SelectedInfo;
public TestGridUC (ref TestClassWithInfo e)
{
InitializeComponent();
m_SelectedInfo = e;
}
}
TestClassWithInfo:
public class TestClassWithInfo
{
public Image imageTest;
public int intTest;
public TestClassWithInfo ()
{
m_img = null;
m_layer = 0;
}
}
I am aware of the existence of DependencyProperty, however, I could not get it to work.
A dependency property really is the way to go about it though:
public partial class TestGridUC : UserControl
{
public TestGridUC()
{
InitializeComponent();
}
public TestClassWithInfo Info
{
get { return (TestClassWithInfo)GetValue(InfoProperty); }
set { SetValue(InfoProperty, value); }
}
public static readonly DependencyProperty InfoProperty =
DependencyProperty.Register("Info", typeof(TestClassWithInfo), typeof(TestGridUC),
new PropertyMetadata(null /*or initialize to a default of new TestClassWithInfo()*/ ));
}
Now you can bind to that property from the xaml in your MainWindow:
<local:TestGridUC
Info="{Binding Info}"></local:TestGridUC>
If you need help with that part, as pr177 answered there are many tutorials on getting started with WPF with the MVVM pattern. The basics here would involve a view model object that contains a TestClassWithInfo public property that you bind to.
Have a look at the MVVM (Model-View-ViewModel) Pattern
There are many tutorials & introductions like that:
https://blogs.msdn.microsoft.com/ivo_manolov/2012/03/17/model-view-viewmodel-mvvm-applications-general-introduction/
or
https://social.technet.microsoft.com/wiki/contents/articles/32164.wpf-mvvm-step-by-step-2.aspx
everybody.
Does anyone knows how to bind global static string to UWP Textblock, with propertychange update in control?
I have tried a lot of things, such as:
Text="{Binding Path={local:AppSettings.StorageFolder}, Mode=OneWay}"
Text="{x:Bind Path=(local:AppSettings.StorageFolder), RelativeSource={RelativeSource Self}, Mode=OneWay}"
And none works. Always some error comes up, like:
"Nested types are not supported and
Value does not fall within the expected range"
I have managed to bind it to non static value in my class:
Text="{x:Bind viewModel.MyValue, Mode=OneWay}"
Any solution to this?
Thanx.
You simply can't bind to a static property in UWP the same way as you can in WPF. There is no x:Static markup extension available.
You have some options:
If the DataContext of the element is an instance of the type in which the static property is defined, you could bind to a static property as usual:
<TextBlock Text="{Binding MyStaticProperty}" />
public sealed partial class BlankPage1 : Page
{
public BlankPage1()
{
this.InitializeComponent();
this.DataContext = this;
}
public static string MyStaticProperty { get { return "Static..."; } }
}
If the static property is defined in another class, your best option would be to wrap the static property in a non-static one:
public sealed partial class BlankPage1 : Page
{
public BlankPage1()
{
this.InitializeComponent();
this.DataContext = this;
}
public static string MyWrapperProperty { get { return MyStaticClass.MyStaticProperty; } }
}
public static class MyStaticClass
{
public static string MyStaticProperty { get { return "Static..."; } }
}
If you want property change notifications it makes no sense to bind to a static property at all because the source object of a property must implement the INotifyPropertyChanged interface for you to be able to refresh the target property dynamically by raising the PropertyChanged event.
You could still wrap the static property in a non-static one of a view model that implements the INotifyPropertyChanged interface:
public class ViewModel : INotifyPropertyChanged
{
public string MyNonStaticProperty
{
get { return MyStaticClass.MyStaticProperty; }
set { MyStaticClass.MyStaticProperty = value; NotifyPropertyChanged(); }
}
//...
}
public static class MyStaticClass
{
public static string MyStaticProperty { get; set; }
}
You will obviosuly need to call NotifyPropertyChanged("MyNonStaticProperty") from the view model class whenever you want to refresh the target property in the view.
Currently in UWP when using x:Bind the end property (I.e. at the end of the path) must be mutable / nonstatic. However you can reference static properties prior to that such as x:Bind local:MyClass.Singleton.NonstaticProperty.
The use of functions at the end of the x:Bind property path can also address this kind of challenge.
I have the following app.xaml:
<BaseApp x:Class ="MyApp"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:MyApp"
Props.MyCustom="test"
Startup ="Application_Startup"
>
<Application.Resources >
< ResourceDictionary>
...
</ ResourceDictionary>
</Application.Resources>
I then need to be able to read the property from the base application:
public partial class BaseApp : Application
{
static void MyFunc()
{
// Access property from here
var myvar = Props.MyCustom
}
}
I'm currently working on the belief that this needs to be in a separate class, as follows:
public class Props : DependencyObject
{
public string MyCustom
{
get { return ( string)GetValue(MyCustomProperty); }
set { SetValue(MyCustomPropertyKey, value); }
}
public static readonly DependencyPropertyKey MyCustomPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("MyCustom" , typeof (string ), typeof (Props), new UIPropertyMetadata (0));
public static readonly DependencyProperty MyCustomProperty = MyCustomPropertyKey.DependencyProperty;
}
Am I approaching this in the right way, and if so, what do I need to do to be able to access this from app.xaml?
EDIT:
For future travellers, the way I finally managed this was to simply declare an abstract read-only property in the base class and override it in code behind. Not as neat as I would have liked, but it works.
Attached properties should have static getter and setter methods, in your case:
public static string GetMyCustom(DependencyObject obj)
{
return (string)obj.GetValue(MyCustomProperty);
}
public static void SetMyCustom(DependencyObject obj, string value)
{
obj.SetValue(MyCustomProperty, value);
}
Having said that you would still have to have an instance of a DependencyObject-derived class to set the property on, which System.Windows.Application is not. In other words, you can't set an attached property on your MyApp object.
Instead of using an attached property, you might just add a plain CLR property to your BaseApp class:
public class BaseApp : Application
{
public string MyCustom { get; set; }
}
and use it like this:
<local:BaseApp x:Class="MyNamespace.MyApp" ...
MyCustom="Hello">
<Application.Resources>
</Application.Resources>
</local:BaseApp>
I have a class called Date which overrides its ToString and returns it via a property:
public string DateString
{
get { return ToString(); }
}
My instance of Date is held in a container class and manipulated from there. In XAML, the overridden ToString is displayed like so:
<TextBlock Text="{Binding Container.Date.DateString, Source={StaticResource Locator}}" />
Locator is defined in App.xaml:
<data:Locator x:Key="Locator" />
The Locator class includes:
public class Locator
{
static Locator()
{
Container = new GameContainer();
}
public static GameContainer Container { get; set; }
}
GameContainer includes:
public class GameContainer
{
public GameContainer()
{
Date = new Date();
}
public Date Date { get; set; }
}
I have also tried this with IOC containers with no luck.
When the program is launched, everything behaves properly; a button click increments the date (and calls RaisePropertyChanged on DateString). However, if I create a new instance of the container or a new instance of Date, the UI no longer updates and I can't figure out why. It only seems to happen after user interaction has occurred; I can create a new instance during various stages of the program's initialization without any problems, but once a button click causes a new instance to be created, the string no longer updates.
I have tried everything I can think of and I can't seem to figure out what is causing this to happen. Any help would be appreciated.
Update: Currently my Dependency Property is as follows:
public static readonly DependencyProperty ContainerProperty =
DependencyProperty.Register("Container", typeof(GameContainer), typeof(Locator));
public static GameContainer Container
{
get { return (GameContainer)GetValue(ContainerProperty); }
set
{
SetValue(ContainerProperty, value);
}
}
I'm getting this error: An object reference is required for the non-static field, method, or property 'System.Windows.DependencyObject.GetValue(System.Windows.DependencyProperty)'
Looks like you don't have any property changed notifying mechanism, this is very important to make the Binding work properly. Try implementing the INotifyPropertyChanged or creating the corresponding DependencyProperty, I'll introduce to using DependencyProperty in this case:
public class GameContainer : DependencyObject {
public GameContainer() {
Date = new Date();
}
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register("Date", typeof(Date), typeof(GameContainer));
public Date Date {
get { return (Date) GetValue(DateProperty);}
set {
SetValue(DateProperty, value);
}
}
}
NOTE: do the same for the property Container in the class Locator.
I'm really confused how Binding deals with static property. This is all I can think of as a should-try code:
public class Locator {
static Locator() {
Container = new GameContainer();
}
public static event EventHandler ContainerChanged;
static GameContainer container;
public static GameContainer Container {
get { return container;}
set {
if(container != value){
container = value;
var handler = ContainerChanged;
handler(null, EventArgs.Empty);
}
}
}
}
If I try "var mainpage new Mainpage()"
I will run the mainpage constructor and then all the fields in the XAML object will return to null. How to I access XAML objects in silverlight that are from a different class but part of the same namespace?
Let me explain by example. If you look at the first answer, here is what I am encountering
public class MyPage
{
MyPage()
{
// the constructor makes all the variables from the xaml null
}
public TextBox MyTextBox
{
get { return SomeTextBox; }
}
}
public class SomeOtherClass
{
private void SomeFunction()
{
var page = new MyPage(); // this makes the text empty
var sometext = page.MyTextBox.Text; // so sometext will be empty
}
}
So whatever the user imputs when the program first runs turns to null when I run SomeFunction.
What I am first going to try is to see if when SomeClass is created, the values are put into that class.
If that fails, I am going to try MVVM. I have seen the http://www.vimeo.com/8915487 video and I got the sample mvvm code
Here is the Model:
namespace SimpleMVVM.Model
{
public class SimpleModel
{
// super easy version
//public string SomeSimpleValue { get; set; }
private string _SomeSimpleValue = string.Empty;
// actually do something version...
public string SomeSimpleValue
{
get
{
return "some value";
}
set
{
_SomeSimpleValue = value;
}
}
}
}
here is the view:
and here is the viewmodel.cs
using Simple;
using SimpleMVVM.Model;
namespace SimpleMVVM.ViewModel
{
public class SimpleViewModel : SimpleViewModelBase
{
private SimpleModel MyModel = new SimpleModel();
public string SomeSimpleValue
{
get { return MyModel.SomeSimpleValue; }
set
{
if (MyModel.SomeSimpleValue != value)
{
MyModel.SomeSimpleValue = value;
RaisePropertyChanged("SomeSimpleValue");
}
}
}
}
}
Using this example, I am wondering if it will just as easy as injecting a ViewModel and then changing the bindings in the Model and the View.
Is MVVM really this easy?
There is one more. It is the viewmodel base class
using System.ComponentModel;
namespace Simple
{
public class SimpleViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var e = new PropertyChangedEventArgs(PropertyName);
PropertyChangedEventHandler changed = PropertyChanged;
if (changed != null) changed(this, e);
}
}
}
OK, so now the hard part. If I create a new class. How do I get the data from the viewmodel class?
First, let me get this rant out of the way: what you propose is very bad design. It fits the definition of smelly code.
If you insist on doing it this way, the "best" approach to take is to declare some public variables on your page that return the actual UI elements.
<UserControl x:Class="MyNamespace.MyPage" ...>
<Grid>
<TextBox x:Name="SomeTextBox" Width="100" />
</Grid>
</UserControl>
public class MyPage
{
public TextBox MyTextBox
{
get { return SomeTextBox; }
}
}
public class SomeOtherClass
{
private void SomeFunction()
{
var page = new MyPage();
page.MyTextBox.Text = "some text";
}
}
Of course the preferred method would be to use something like the MVVM pattern to implement binding from your window to its viewmodel, then you can just read the property values from the viewmodel, this way you avoid trying to touch any UI elements from a totally different class.
Another way to do it (without going the full MVVM route) is to inject the necessary values into the constructor of the control/page that you are instantiating, and from there you can assign them to the appropriate UI element properties. This is still smelly, but better than directly accessing the UI elements from the outside.