My WPF custom control's Data Context is superseding parent's - c#

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}" ... />

Related

Binding tab controls with mahapps and prism - WPF

I am building a WPF application with mahapps, prism[modularity]. I have below HomeWindow.xaml code.
<Controls:MetroWindow x:Class="Project.Views.HomeWindow"
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:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:Project.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
<!--The above code is for automatically binding of viewmodel into view-->
Height="700" Width="1200" Background="White">
<Grid>
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="{Binding Name}"/>
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Controls:MetroWindow>
I have below structure in my HomeViewModel.cs under ViewModels directory.
public class HomeViewModel : BindableBase
{
private ObservableCollection<Item> _tabCollection;
public ObservableCollection<Item> TabCollection { get { return _tabCollection; } set { SetProperty(ref _tabCollection, value); } }
//Prism way of getting and setting data
}
public class Item
{
private string Name;
private string Content;
public Item(string name, string content)
{
Name = name;
Content = content;
}
}
below is how I add data into TabCollection property through HomeWindow.xaml.cs.
private HomeViewModel _model=new HomeViewModel();
public HomeWindow(EmployeeViewModel model)
{
InitializeComponent();
_model.UserViewModel = model;
LoadHomeData(_model.UserViewModel.EmpRole);
DataContext = this;
}
private void LoadHomeData(string Role)
{
if (string.Equals(Role, "Admin"))
{
_model.TabCollection= new ObservableCollection<Item>()
{
new Item("Test1", "1"),
new Item("Test2", "2"),
new Item("Test3", "3")
};
}
}
Now matter what, the tabs will not get displayed. Its a blank empty window. I have followed the example in the issue here and have went through few similar posts having same kind of approach. But none of them helped. Is this because of prism way of databinding or is there anything else am missing here? Hope to find some help on this..
Your problem is not connected to MahApps or Prism but to how WPF works in general. In your case Name and Content are private fields and should be public properties
public string Name { get; set; }
public string Content { get; set; }
private or field is not a valid binding source. You can find more as to what is a valid binding source under Binding Sources Overview but in your case, as far as CLR object goes:
You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object. The binding engine uses CLR reflection to get the values of the properties. Alternatively, objects that implement ICustomTypeDescriptor or have a registered TypeDescriptionProvider also work with the binding engine.
Another problem is that DataContext is set wrong. At the moment is set to HomeWindow and I think it should be set to instance of HomeViewModel which holds TabCollection property
DataContext = _model;

Can I bind a WPF control to a field's property?

Because I needed to split some functionality between classes, I've arrived at the following situation
xaml code
<CheckBox IsChecked="{Binding MyObjectField.MyBoolean}" />
view model
...
public MyInternalObject MyObjectField;
...
MyObject class
public class MyInternalObject {
...
public bool MyBoolean { get; set; }
...
}
It does not work unless I replicate the MyBoolean property in the View Model class.
public bool MyBoolean
{
get { return MyInternalObject.MyBoolean; }
set { MyInternalObject.MyBoolean=value; }
}
Does anyone have an idea?
You can't yet (in WPF Version 4.5 you can bind to a static property). But you can create your property in App.xaml.cs
public partial class App : Application
{
public bool MyBoolean { get; set; }
}
and bind from everywhere.
<CheckBox IsChecked="{Binding MyBoolean, Source={x:Static Application.Current}}">
No you cant . Because binding system uses Reflection to find the
Property in DataContext(i.e your VM)
It does not look for fields . I hope this will help.
Instead of binding an element to a field's property I changed the DataContext of the element to the required field.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindowView mainWindowView = new MainWindowView();
var mainWindowViewModel = new MainWindowViewModel();
mainWindowView.DataContext = mainWindowViewModel;
mainWindowView.pagerView.DataContext = mainWindowViewModel.pager;
mainWindowView.Show();
}
In this example I have a DataGrid and Pager (first, prev, next, last page) below it. The elements of the MainWindowView (including the DataGrid) are binded to properties in the MainWindowViewModel but the pager buttons are binded to the properties of mainWindowViewModel.pager.
MainWindowView:
<DataGrid Name="dgSimple" ItemsSource="{Binding DisplayedUsers}" MaxWidth="200" Grid.Row="0" SelectedItem="{Binding SelectedRow}"></DataGrid>
<view:PagerView x:Name="pagerView" Grid.Row="2"/>
PagerView:
<UserControl x:Class="wpf_scroll.View.PagerView"
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"
xmlns:local="clr-namespace:wpf_scroll.View"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="350">
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Label Content="Page size:"/>
<TextBox Text="{Binding PageSize}" Width="30" VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"></TextBox>
<Button Content="First" Command="{Binding FirstPageCommand}"></Button>

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.

WPF checkbox binding

While it is trivial to store a checkbox's checked state in a variable using the checkbox's Click event, how would I do it via databinding? All the examples I have found have the UI updated from some datasource, or bind one control to another; I want to update a member variable when the checkbox is clicked.
TIA for any pointers...
You must make your binding bidirectional :
<checkbox IsChecked="{Binding Path=MyProperty, Mode=TwoWay}"/>
You need a dependency property for this:
public BindingList<User> Users
{
get { return (BindingList<User>)GetValue(UsersProperty); }
set { SetValue(UsersProperty, value); }
}
public static readonly DependencyProperty UsersProperty =
DependencyProperty.Register("Users", typeof(BindingList<User>),
typeof(OptionsDialog));
Once that is done, you bind the checkbox to the dependency property:
<CheckBox x:Name="myCheckBox"
IsChecked="{Binding ElementName=window1, Path=CheckBoxIsChecked}" />
For that to work you have to name your Window or UserControl in its openning tag, and use that name in the ElementName parameter.
With this code, whenever you change the property on the code side, you will change the textbox. Also, whenever you check/uncheck the textbox, the Dependency Property will change too.
EDIT:
An easy way to create a dependency property is typing the snippet propdp, which will give you the general code for Dependency Properties.
All the code:
XAML:
<Window x:Class="StackOverflowTests.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" x:Name="window1" Height="300" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<CheckBox Margin="10"
x:Name="myCheckBox"
IsChecked="{Binding ElementName=window1, Path=IsCheckBoxChecked}">
Bound CheckBox
</CheckBox>
<Label Content="{Binding ElementName=window1, Path=IsCheckBoxChecked}"
ContentStringFormat="Is checkbox checked? {0}" />
</StackPanel>
</Grid>
</Window>
C#:
using System.Windows;
namespace StackOverflowTests
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public bool IsCheckBoxChecked
{
get { return (bool)GetValue(IsCheckBoxCheckedProperty); }
set { SetValue(IsCheckBoxCheckedProperty, value); }
}
// Using a DependencyProperty as the backing store for
//IsCheckBoxChecked. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckBoxCheckedProperty =
DependencyProperty.Register("IsCheckBoxChecked", typeof(bool),
typeof(Window1), new UIPropertyMetadata(false));
public Window1()
{
InitializeComponent();
}
}
}
Notice how the only code behind is the Dependency Property. Both the label and the checkbox are bound to it. If the checkbox changes, the label changes too.
Hello this is my first time posting so please be patient:
my answer was to create a simple property:
public bool Checked { get; set; }
Then to set the data context of the Checkbox (called cb1):
cb1.DataContext = this;
Then to bind the IsChecked proerty of it in the xaml
IsChecked="{Binding Checked}"
The code is like this:
XAML
<CheckBox x:Name="cb1"
HorizontalAlignment="Left"
Margin="439,81,0,0"
VerticalAlignment="Top"
Height="35" Width="96"
IsChecked="{Binding Checked}"/>
Code behind
public partial class MainWindow : Window
{
public bool Checked { get; set; }
public MainWindow()
{
InitializeComponent();
cb1.DataContext = this;
}
private void myyButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Checked.ToString());
}
}
Should be easier than that. Just use:
<Checkbox IsChecked="{Binding Path=myVar, UpdateSourceTrigger=PropertyChanged}" />
if you have the property "MyProperty" on your data-class, then you bind the IsChecked like this.... (the converter is optional, but sometimes you need that)
<Window.Resources>
<local:MyBoolConverter x:Key="MyBoolConverterKey"/>
</Window.Resources>
<checkbox IsChecked="{Binding Path=MyProperty, Converter={StaticResource MyBoolConverterKey}}"/>
This works for me (essential code only included, fill more for your needs):
In XAML a user control is defined:
<UserControl x:Class="Mockup.TestTab" ......>
<!-- a checkbox somewhere within the control -->
<!-- IsChecked is bound to Property C1 of the DataContext -->
<CheckBox Content="CheckBox 1" IsChecked="{Binding C1, Mode=TwoWay}" />
</UserControl>
In code behind for UserControl
public partial class TestTab : UserControl
{
public TestTab()
{
InitializeComponent(); // the standard bit
// then we set the DataContex of TestTab Control to a MyViewModel object
// this MyViewModel object becomes the DataContext for all controls
// within TestTab ... including our CheckBox
DataContext = new MyViewModel(....);
}
}
Somewhere in solution class MyViewModel is defined
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool m_c1 = true;
public bool C1 {
get { return m_c1; }
set {
if (m_c1 != value) {
m_c1 = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("C1"));
}
}
}
}
No backend and ViewModel Code:
I made such check box to control other control's visibility.
<CheckBox x:Name="rulerCheckbox" Content="Is Ruler Visible" IsChecked="True"/>
and in the other control, I added such binding:
Visibility="{Binding IsChecked, ElementName=rulerCheckbox, Mode=TwoWay, Converter={StaticResource BoolVisConverter}}">

Access codebehind variable in XAML

How can I access the public variable which in Sample.xaml.cs file like asp.net <%=VariableName%>?
There are a few ways to do this.
Add your variable as a resource from codebehind:
myWindow.Resources.Add("myResourceKey", myVariable);
Then you can access it from XAML:
<TextBlock Text="{StaticResource myResourceKey}"/>
If you have to add it after the XAML gets parsed, you can use a DynamicResource above instead of StaticResource.
Make the variable a property of something in your XAML. Usually this works through the DataContext:
myWindow.DataContext = myVariable;
or
myWindow.MyProperty = myVariable;
After this, anything in your XAML can access it through a Binding:
<TextBlock Text="{Binding Path=PropertyOfMyVariable}"/>
or
<TextBlock Text="{Binding ElementName=myWindow, Path=MyProperty}"/>
For binding, if DataContext is not in use, you can simply add this to the constructor of the code behind:
this.DataContext = this;
Using this, every property in the code becomes accessible to binding:
<TextBlock Text="{Binding PropertyName}"/>
Another way is to just give a name to the root element of the XAML:
x:Name="root"
Since the XAML is compiled as a partial class of the code-behind, we can access every property by name:
<TextBlock Text="{Binding ElementName="root" Path=PropertyName}"/>
Note: access is only available to properties; not to fields. set; and get; or {Binding Mode = OneWay} are necessary. If OneWay binding is used, the underlying data should implement INotifyPropertyChanged.
For quick-and-dirty Windows in WPF, I prefer binding the DataContext of the Window to the window itself; this can all be done in XAML.
Window1.xaml
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource self}}"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TextBlock Text="{Binding Path=MyProperty1}" />
<TextBlock Text="{Binding Path=MyProperty2}" />
<Button Content="Set Property Values" Click="Button_Click" />
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
public static readonly DependencyProperty MyProperty2Property =
DependencyProperty.Register("MyProperty2", typeof(string), typeof(Window1), new UIPropertyMetadata(string.Empty));
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.Register("MyProperty1", typeof(string), typeof(Window1), new UIPropertyMetadata(string.Empty));
public Window1()
{
InitializeComponent();
}
public string MyProperty1
{
get { return (string)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
public string MyProperty2
{
get { return (string)GetValue(MyProperty2Property); }
set { SetValue(MyProperty2Property, value); }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Set MyProperty1 and 2
this.MyProperty1 = "Hello";
this.MyProperty2 = "World";
}
}
In the above example, note the binding used in the DataContext property on the Window, this says "Set your data context to yourself". The two text blocks are bound to MyProperty1 and MyProperty2, the event handler for the button will set these values, which will automatically propagate to the Text property of the two TextBlocks as the properties are Dependency Properties.
It is also worth noting that a 'Binding' can only be set on a DependencyProperty of a DependencyObject. If you want to set a non DependencyProperty (eg. a normal property) on an object in XAML, then you will have to use Robert's first method of using resources in the code behind.
myWindow.xaml
<Window
...
<TextBlock Text="{ Binding Path=testString }" />
</Window>
myWindow.xaml.cs
public partial class myWindow: Window
{
public string testString { get; set; } = "This is a test string";
public myWindow()
{
DataContext = this;
InitializeComponent();
}
}
Important
Set Datacontext
testString MUST be public
testString MUST be a property (have a get and set)

Categories

Resources