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

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>

Related

Binding to SelectedItem of ComboBox in UserControl

I have a UserControl consisting of a ComboBox with a Label. I am looking to update a screen with an instance of this ComboBox and dynamically create UserControls in a StackPanel, based on the SelectedItem value.
I currently have a screen with an instance of this ComboBox and have it binding the following way:
Pseudocode Example (removing unrelated code):
<!-- MyComboBoxExample.xaml -->
<ComboBox x:Name="myComboBox" SelectedValuePath="Key" DisplayMemberPath="Value" ItemsSource="{Binding MyBoxItems}/>
/* MyComboBoxExample.xaml.cs */
public static readonly DependencyProperty MyBoxItemsProperty = DependencyProperty.Register("MyBoxItems", typeof(Dictionary<string, string>),
typeof(MyComboBoxExample), new PropertyMetadata(null));
<!-- MyScreen.xaml -->
<local:MyComboBoxExample x:Name="MyComboBoxExampleInstance" MyBoxItems="{Binding Descriptions}"/>
I am new to WPF and databinding, so not sure the best way to implement this. Basically, on the screen: when MyComboBoxExampleInstance selection changes, dynamically set the controls of a StackPanel on the screen. I am not sure how to properly hook in to the SelectionChanged event of a child object of a UserControl.
Any thoughts, corrections, and (constructive) criticism is appreciated. Thanks for any help in advance.
There are several ways to go about this. Here's one way. It's not necessarily the best way but it's easy to understand.
First, the user control xaml. Note the binding of the ItemsSource property on the user control, which specifies MyComboBoxItems as the items source. More on where that comes from in a bit.
<UserControl x:Class="WpfApp1.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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ComboBox Height="Auto" ItemsSource="{Binding MyComboBoxItems}" SelectionChanged="OnSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
Now the code-behind, MyUserControl.xaml.cs. We provide an combobox selection changed event handler that in turn raises a custom event, MyComboBoxSelectionChanged, which is defined by the event argument class and delegate handler at the bottom of the code. Our OnSelectionChanged method simply forwards the selection change event via the custom event we've defined.
using System;
using System.Windows.Controls;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MyUserControl.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public event MyComboBoxSelectionChangedEventHandler MyComboBoxSelectionChanged;
public MyUserControl()
{
InitializeComponent();
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
MyComboBoxSelectionChanged?.Invoke(this,
new MyComboBoxSelectionChangedEventArgs() {MyComboBoxItem = e.AddedItems[0]});
}
}
}
public class MyComboBoxSelectionChangedEventArgs : EventArgs
{
public object MyComboBoxItem { get; set; }
}
public delegate void MyComboBoxSelectionChangedEventHandler(object sender, MyComboBoxSelectionChangedEventArgs e);
}
Now we go to our MainWindow.xaml, where we define an instance of MyUserControl and set a handler for the custom event we defined. We also provide a StackPanel to host the items that will be created on a selection changed event.
<Window x:Class="WpfApp1.MainWindow"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<local:MyUserControl Width="140" Height="32" DataContext="{Binding}" Grid.Row="0" MyComboBoxSelectionChanged="OnSelectionChanged"></local:MyUserControl>
<StackPanel Grid.Row="1" x:Name="MyUserControls"/>
</Grid>
</Window>
Now the code-behind for MainWindow.xaml. Here we define a public property containing a list of objects of type MyComboBoxItem (defined at the bottom of the file), and we initialize the array with some values.
Recall that we set the ItemsSource property of the ComboBox inside MyUserControl to "{Binding MyComboBoxItems}", so the question is, how does the property defined in the MainWindow magically become available in MyUserControl?
In WPF, DataContext values are inherited from parent controls if they aren't explicitly set, and since we did not specify a data context for the control, the instance of MyUserControl inherits the DataContext of the parent window. In the constructor we set the MainWindow data context to refer to itself, so the MyComboBoxItems list is available to any child controls (and their children, and so on.)
Typically we'd go ahead and add a dependency property for the user control called ItemsSource and in the user control we'd bind the ComboBox's ItemsSource property to the dependency property rather than to MyComboxItems.
MainWindow.xaml would then bind it's collection directly to the dependency property on the user control. This helps make the user control more re-usable since it wouldn't depend on specific properties defined in an inherited data context.
Finally, in the event handler for the user control's custom event we obtain the value selected by the user and create a UserControl populated with a text box (all with various properties set to make the items interesting visually) and we directly add them to the Children property of the StackPanel.
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public List<MyComboBoxItem> MyComboBoxItems { get; set; } = new List<MyComboBoxItem>()
{
new MyComboBoxItem() {Text = "Item1"},
new MyComboBoxItem() {Text = "Item2"},
new MyComboBoxItem() {Text = "Item3"},
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnSelectionChanged(object sender, MyComboBoxSelectionChangedEventArgs e)
{
if (e.MyComboBoxItem is MyComboBoxItem item)
{
MyUserControls.Children.Add(
new UserControl()
{
Margin = new Thickness(2),
Background = new SolidColorBrush(Colors.LightGray),
Content = new TextBlock()
{
Margin = new Thickness(4),
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
FontSize = 48,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(Colors.DarkGreen),
Text = item.Text
}
});
}
}
}
public class MyComboBoxItem
{
public string Text { get; set; }
}
}
Finally, I'd consider using an ItemsControl or a ListBox bound to an ObservableCollection rather than sticking things into a StackPanel. You could define a nice data template for the user control to display and maybe a DataTemplateSelector to use different user controls based on settings in the data item. This would allow me to simply add the reference to the MyComboBoxItem obtained in the selection changed handler to that collection, and the binding machinery would automatically generate a new item using the data template I defined and create the necessary visual elements to display it.
So given all that, here are the changes to do all that.
First, we modify our data item to add a color property. We'll use that property to determine how we display the selected item:
public class MyComboBoxItem
{
public string Color { get; set; }
public string Text { get; set; }
}
Now we implement INotifyPropertyChanged in MainWindow.xaml.cs to let the WPF binding engine update the UI when we change properties. This is the event handler and a helper method, OnPropertyChanged.
We also modify the combo box initializer to add a value for the Color property. We'll leave on blank for fun.
We then add a new ObservableCollect, "ActiveUserControls" to store the MyComboBoxItem received in the combo box selection changed event. We do that instead of creating user controls on the fly in code.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public List<MyComboBoxItem> MyComboBoxItems { get; set; } = new List<MyComboBoxItem>()
{
new MyComboBoxItem() {Text = "Item1", Color = "Red"},
new MyComboBoxItem() {Text = "Item2", Color = "Green"},
new MyComboBoxItem() {Text = "Item3"},
};
private ObservableCollection<MyComboBoxItem> _activeUserControls;
public ObservableCollection<MyComboBoxItem> ActiveUserControls
{
get => _activeUserControls;
set { _activeUserControls = value; OnPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnSelectionChanged(object sender, MyComboBoxSelectionChangedEventArgs e)
{
if (e.MyComboBoxItem is MyComboBoxItem item)
{
if (ActiveUserControls == null)
{
ActiveUserControls = new ObservableCollection<MyComboBoxItem>();
}
ActiveUserControls.Add(item);
}
}
}
Now let's look at some changes we made to MyUserControl. We've modified the combo box ItemsSource to point at a property, ItemsSource defined in MyUserControl, and we also map the ItemTemplate to an ItemTemplate property in MyUserControl.
<UserControl x:Class="WpfApp1.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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid>
<ComboBox Height="Auto"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyUserControl}}}"
ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyUserControl}}}"
SelectionChanged="OnSelectionChanged">
</ComboBox>
</Grid>
</UserControl>
Here's were we define those new properties in MyUserControl.cs.
public partial class MyUserControl : UserControl
{
public event MyComboBoxSelectionChangedEventHandler MyComboBoxSelectionChanged;
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource",
typeof(System.Collections.IEnumerable),
typeof(MyUserControl),
new PropertyMetadata(null));
public System.Collections.IEnumerable ItemsSource
{
get => GetValue(ItemsSourceProperty) as IEnumerable;
set => SetValue(ItemsSourceProperty, (IEnumerable)value);
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate",
typeof(DataTemplate),
typeof(MyUserControl),
new PropertyMetadata(null));
public DataTemplate ItemTemplate
{
get => GetValue(ItemTemplateProperty) as DataTemplate;
set => SetValue(ItemTemplateProperty, (DataTemplate)value);
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
MyComboBoxSelectionChanged?.Invoke(this,
new MyComboBoxSelectionChangedEventArgs() {MyComboBoxItem = e.AddedItems[0]});
}
}
}
Let's look at how we bind to those in MainWindow.xaml:
<local:MyUserControl Width="140"
Height="32"
Grid.Row="0"
MyComboBoxSelectionChanged="OnSelectionChanged"
ItemsSource="{Binding MyComboBoxItems}"
ItemTemplate="{StaticResource ComboBoxItemDataTemplate}" />
So now we can bind our items directly and provide our own data template to specify how the combobox should display the item.
Finally, I want to replace the StackPanel with an ItemsControl. This is like a ListBox without scrolling or item selection support. In fact, ListBox is derived from ItemsControl. I also want to use a different user control in the list based on the value of the Color property. To do that, we define some data templates for each value in MainWindow.Xaml:
<DataTemplate x:Key="ComboBoxItemDataTemplate"
DataType="local:MyComboBoxItem">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="4"
Text="{Binding Text}" />
<TextBlock Margin="4"
Text="{Binding Color}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="GreenUserControlDataTemplate"
DataType="local:MyComboBoxItem">
<local:GreenUserControl DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="RedUserControlDataTemplate"
DataType="local:MyComboBoxItem">
<local:RedUserControl DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="UnspecifiedUserControlDataTemplate"
DataType="local:MyComboBoxItem">
<TextBlock Margin="4"
Text="{Binding Text}" />
</DataTemplate>
Here's RedUserControl. Green is the same with a different foreground color.
<UserControl x:Class="WpfApp1.RedUserControl"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Background="LightGray"
Margin="2">
<TextBlock Margin="4"
Foreground="DarkRed"
TextWrapping="Wrap"
Text="{Binding Text}"
FontSize="24"
FontWeight="Bold" />
</Grid>
</UserControl>
Now the trick is to use the right data template based on the color value. For that we create a DataTemplateSelector. This is called by WPF for each item to be displayed. We can examine the data context object a choose which data template to use:
public class UserControlDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (container is FrameworkElement fe)
{
if (item is MyComboBoxItem cbItem)
{
if (cbItem.Color == "Red")
{
return fe.FindResource("RedUserControlDataTemplate") as DataTemplate;
}
if (cbItem.Color == "Green")
{
return fe.FindResource("GreenUserControlDataTemplate") as DataTemplate;
}
return fe.FindResource("UnspecifiedUserControlDataTemplate") as DataTemplate;
}
}
return null;
}
}
We create an instance of our data template selector in xaml in MainWindow.xaml:
<Window.Resources>
<local:UserControlDataTemplateSelector x:Key="UserControlDataTemplateSelector" />
...
Finally we replace our stack panel with an Items control:
<ItemsControl Grid.Row="1"
x:Name="MyUserControls"
ItemsSource="{Binding ActiveUserControls}"
ItemTemplateSelector="{StaticResource UserControlDataTemplateSelector}" />

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;

My WPF custom control's Data Context is superseding parent's

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

How to bind a Combobox ItemSource to a Property not in the DataContext?

I have a Dialog box, ConfigSetup that has a Combobox. Its data context is set to the viewModel, but I need to bind the ItemSource of my Combobox to a property in the main window( MainWindow).
public partial class MainWindow : Window, INotifyPropertyChanged
{
...
public CfgData.TMicMode[] MicModeOptions
{
get
{
return (CfgData.TMicMode[])System.Enum.GetValues(typeof(CfgData.TMicMode));
}
}
}
Here's where the viewModel is setup in the dialog box code
public partial class ConfigSetup : Window, INotifyPropertyChanged
{
private ConfigSetupVM vm_ = null;
public ConfigSetup(CfgData cfgData)
{
vm_ = new ConfigSetupVM(cfgData);
InitializeComponent();
vm_.RequestClose += delegate
{
Close();
};
DataContext = vm_;
}
}
Here's the code in the VM that has the selectedvalue property to bind to
class ConfigSetupVM : ViewModelBase, IDataErrorInfo
{
...
/// <summary>
/// C-5000's microphone mode.
/// </summary>/
public CfgData.TMicMode MicMode
{
get { return model_.MicMode; }
set { model_.MicMode = value; NotifyPropertyChanged("MicMode"); }
}
Here's the XAML with the combobox
<Window x:Class="RpP25.ConfigSetup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:RpWin="clr-namespace:RpP25"
Title="FCT Configuration"
Width="300"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow"
FocusManager.FocusedElement="{Binding ElementName=name}"
Background="AliceBlue" >
<Window.Resources>
...
</Window.Resources>
...
<ComboBox Grid.Row="6" Grid.Column="1"
HorizontalAlignment="Right" MinWidth="75"
ItemsSource="{Binding RpWin:MainWindow.MicModeOptions, Mode=OneWay}"
SelectedValue="{Binding RpWin:MainWindow.MicMode, Mode=TwoWay, TargetNullValue=Not Selected,
ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" />
...
I know I'm missing something fundamental to Binding, but I can't for the life of figure out how to bind to something outside the datacontext.
I've tried to use FindAncestor... with no success
You help would be greatly appreciated.
There are two possible ways. The one is, as the code below, to use the static member.
<ComboBox ItemsSource="{Binding Source={x:Static local:MainWindow.MicModeOptions} , Mode=OneWay}"/>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public **static** CfgData.TMicMode[] MicModeOptions
{
}
}
The other is to use Resources in XAML, where the target class(MainWindow in your code) has to get a default constructor(parameterless).
<Grid>
<Grid.Resources>
<local:MainWindow x:Key="mainWindow"/>
</Grid.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource mainWindow}, Path=MicModeOptions , Mode=OneWay}"/>
</Grid>
How is the dialog window launched? If it is launched via window.ShowDialog() then you could pass the necessary object you need to bind to as a parameter to the constructor of your dialog window. The constructor then assigns it to an internal property to which your XAML code can bind to.
Try this method, easy and clean.
<!-- In user countrol resources -->
<UserControl.Resources>
<CollectionViewSource Source="{Binding Currencies}" x:Key="Currencies"/>
</UserControl.Resources>
<!-- below inside ex. DataGrid -->
<ComboBox ItemsSource="{Binding Source={StaticResource Currencies}}" IsSynchronizedWithCurrentItem="False"
DisplayMemberPath="IsoCode"
SelectedItem="{Binding BaseCurrency}"/>
<!-- IsSynchronizedWithCurrentItem="False" is important, otherwise ComboBoxes will select same item for each child viewmodel -->
reference to blogpost http://kostylizm.blogspot.ru/2014/04/wpf-combobox-itemssource-bind-to-parent.html

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

Categories

Resources