How do I create a custom control that accepts <Run/> text? - c#

Given the control below, how do I modify it to accept "Run" text?
Custom Control:
[ContentProperty("Text")]
public class GradientTitle : Control
{
public GradientTitle()
{
this.DefaultStyleKey = typeof(GradientTitle);
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(GradientTitle), new PropertyMetadata(null));
}
Intended use:
<customControls:GradientTitle>
<Run Text="The quick brown fox" />
<Run Text="jumps over the lazy dog" />
<Run Text="{Binding SomeText}" />
</customControls:GradientTitle>

You probably shouldn't do this, as TextBlock already does it, but anyway:
[ContentProperty("Inlines")]
[TemplatePart(Name = "PART_InlinesPresenter", Type = typeof(TextBlock))]
public class GradientTitle : Control
{
private readonly Collection<Inline> _inlines = new Collection<Inline>();
public Collection<Inline> Inlines
{
get { return _inlines; }
}
static GradientTitle()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(GradientTitle),
new FrameworkPropertyMetadata(typeof(GradientTitle)));
}
public override void OnApplyTemplate()
{
base.ApplyTemplate();
var inlinesPresenter = GetTemplateChild("PART_InlinesPresenter") as TextBlock;
if(inlinesPresenter != null)
{
var targetInlines = inlinesPresenter.Inlines;
foreach(var inline in Inlines)
{
targetInlines.Add(inline);
}
}
}
}
To simplify solution, I'm using TextBlock to render inline objects and declaring Inlines as a simple (non-dependency) property (almost as TextBlock does - it's Inlines property is not bindable without some external help). Also I don't track any collection changes. All these missing features can be added if needed, but require too much code for a simple answer.
Usage in XAML:
<Grid>
<FrameworkElement.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type local:GradientTitle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GradientTitle}">
<TextBlock x:Name="PART_InlinesPresenter" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</FrameworkElement.Resources>
<customControls:GradientTitle>
<Run Text="TEST1" />
<LineBreak />
<Run Text="TEST2" />
<LineBreak />
<Run Text="{Binding Path=Title, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</customControls:GradientTitle>
</Grid>
Obviously, style can be declared somewhere else.

Related

Dependency property is not updating the view

I have an ItemsControl that is binding to an ObservableCollection "MenuButtons".
In the ItemsControl, I want to add some Buttons programmatically with Dependency Properties.
My problem is that the values I pass are not updated. The default values are displayed in the view.
C#
private void btnTest_Click(object sender, RoutedEventArgs e)
{
var vm = DataContext as UflMainWindowViewModel;
vm.MenuButtons.Add(new UflMenuButton { IconText="Test123", Style = (Style)Application.Current.Resources["UflMenuButtonStyle"] });
}
C# UflButtonClass
public class UflMenuButton : Button
{
public string IconText
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("IconText", typeof(string), typeof(UflMenuButton), new UIPropertyMetadata("default", new PropertyChangedCallback(IconTextChanged)));
private static void IconTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UflMenuButton button = (UflMenuButton)sender;
button.IconText = (string)e.NewValue;
}
}
with the following Style:
WPF
<Style x:Key="UflMenuButtonStyle" TargetType="{x:Type local:UflMenuButton}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=(local:UflMenuButton.IconText),RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
You can bind to the first ancestor of type UflMenuButton :
<TextBlock Text="{Binding IconText, RelativeSource={RelativeSource AncestorType={x:Type local:UflMenuButton}}}" HorizontalAlignment="Center"/>
This work, but I assume a more elegant way exists.

Can I make this block of XAML into a reusable "control"?

I have a Grid, and in that grid, I have this:
<StackPanel Grid.Row="2"
Grid.Column="0">
<Grid x:Name="GridButtonItem" Margin="30,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background"
Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
Value="#332a8dd4" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="False">
<Setter Property="Background"
Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Margin="3"
Source="{dx:DXImageOffice2013 Image=Windows_32x32.png}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Margin="10,3,3,0"
Text="Application Log" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Margin="10,0,3,3"
Text="C:\Program Files (x86)\ATI Technologies\ATI.ACE\MOM-InstallProxy" />
</Grid>
</StackPanel>
The StackPanel is actually meant to hold many of the GridButtonItem items. Is there a way that I can somehow make a "template" of GridButtonItem and then for each one I want to add to the StackPanel, just set the Image and Text properties?
Something like this (just pseudo-code for demonstration):
<StackPanel>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
</StackPanel>
So each one that is added picks up the row/column definitions, and an embedded Image and two TextBlocks. Then I just set the three properties for each one added.
Is this possible?
You can put your grid control into a UserControl and then reuse the UserControl throughout your project. I have a simple example of doing this with a label and Textbox.
here is the XAML:
<UserControl x:Class="TestVision.CustomControls.LabelAndTextbox"
x:Name="parent"
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:TestVision.CustomControls"
mc:Ignorable="d" >
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=parent}">
<TextBlock Text="{Binding Path=Label}" Width="{Binding Path=LabelWidth}" VerticalAlignment="Center" TextAlignment="Right" Margin="0,0,10,0" Height="22"/>
<TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" Width="{Binding Path=TextboxWidth}" IsReadOnly="{Binding Path=TextboxReadOnly, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="{Binding Path=TextboxHorizontalContentAlgnment}"/>
</StackPanel>
</UserControl>
Any properties that you want to be able to set e.g. your image text etc. must be bound to Dependency Properties in the code behind.
Code behind:
public partial class LabelAndTextbox : UserControl
{
/// <summary>
/// Gets or sets the Label which is displayed next to the field
/// </summary>
public String Label
{
get { return (String)GetValue(LabelContent); }
set { SetValue(LabelContent, value); }
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContent =
DependencyProperty.Register("Label", typeof(string),
typeof(LabelAndTextbox), new PropertyMetadata(""));
public object Text
{
get { return (object)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(object),
typeof(LabelAndTextbox), new PropertyMetadata(null));
public Double LabelWidth
{
get { return (Double)GetValue(LabelWidthProperty); }
set { SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register("LabelWidth", typeof(Double),
typeof(LabelAndTextbox), new PropertyMetadata());
public Double TextboxWidth
{
get { return (Double)GetValue(TextboxWidthProperty); }
set { SetValue(TextboxWidthProperty, value); }
}
public static readonly DependencyProperty TextboxWidthProperty =
DependencyProperty.Register("TextboxWidth", typeof(Double),
typeof(LabelAndTextbox), new PropertyMetadata());
public bool TextboxReadOnly
{
get { return (bool)GetValue(TextboxReadOnlyProperty); }
set { SetValue(TextboxReadOnlyProperty, value); }
}
public static readonly DependencyProperty TextboxReadOnlyProperty =
DependencyProperty.Register("TextboxReadOnly", typeof(bool),
typeof(LabelAndTextbox), new FrameworkPropertyMetadata());
public HorizontalAlignment TextboxHorizontalContentAlgnment
{
get { return (HorizontalAlignment)GetValue(TextboxHorizontalContentAlgnmentProperty); }
set { SetValue(TextboxHorizontalContentAlgnmentProperty, value); }
}
public static readonly DependencyProperty TextboxHorizontalContentAlgnmentProperty =
DependencyProperty.Register("TextboxHorizontalContentAlgnment", typeof(HorizontalAlignment),
typeof(LabelAndTextbox), new FrameworkPropertyMetadata());
public LabelAndTextbox()
{
InitializeComponent();
}
}
you then will need to add a reference in the XAML file to your UserControl like this:
xmlns:Resource="clr-namespace:ProjectNamespace.FolderContainingYourControl"
Resource is a generic identifier you can call it what you like, you can then reference your control in the like this:
<Resource:LabelAndTextblock x:Name="AddressLine1" Label="{Binding LblTxt_AddressLine1}" Text="{Binding AddressLine1, Mode=TwoWay}" Margin="10,5,0,5" LabelWidth="70" TextWidth="250" TextHeight="60"/>
You could do this with a UserControl (two different ways) or a DataTemplate. Let's go with DataTemplate, because stuicidle already ably demonstrated one UserControl approach.
There are a couple of different ways to do this with a DataTemplate, too.
We're going to do something called an implicit DataTemplate. It's created in Resources, but it has no x:Key property, just a DataType="{x:Type local:GridItemViewModel}" property. What that will do is this: Wherever that DataTemplate is in scope, whenever XAML needs to display a GridItemViewModel and nothing is specifying a template to display it in, it'll use that implicit template.
Clear as mud! Welcome to the XAML learning curve.
ViewModels.cs
using System;
using System.ComponentModel;
using System.Windows.Media;
namespace GridItemAnswer
{
#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class
#region GridItemViewModel Class
public class GridItemViewModel : ViewModelBase
{
#region LabelText Property
private String _labelText = null;
public String LabelText
{
get { return _labelText; }
set
{
if (value != _labelText)
{
_labelText = value;
OnPropertyChanged();
}
}
}
#endregion LabelText Property
#region Path Property
private String _path = null;
public String Path
{
get { return _path; }
set
{
if (value != _path)
{
_path = value;
OnPropertyChanged();
}
}
}
#endregion Path Property
#region ImageSource Property
private ImageSource _imageSource = null;
public ImageSource ImageSource
{
get { return _imageSource; }
set
{
if (value != _imageSource)
{
_imageSource = value;
OnPropertyChanged();
}
}
}
#endregion ImageSource Property
}
#endregion GridItemViewModel Class
}
MainWindow.xaml
<Window
x:Class="GridItemAnswer.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:GridItemAnswer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<DataTemplate DataType="{x:Type local:GridItemViewModel}">
<StackPanel>
<Grid x:Name="GridButtonItem" Margin="30,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#332a8dd4" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Margin="3"
Source="{Binding Image}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="10,3,3,0"
Text="{Binding LabelText}"
/>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="10,0,3,3"
Text="{Binding Path}"
/>
</Grid>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ItemsControl>
<local:GridItemViewModel
LabelText="Foo Bar"
Path="c:\foo\bar"
/>
<local:GridItemViewModel
LabelText="Baz Planxty"
Path="c:\baz\planxty"
/>
</ItemsControl>
<Label>
<local:GridItemViewModel
LabelText="A frog walks into a bank asking for a loan"
Path="c:\knick\knack"
/>
</Label>
</StackPanel>
</Grid>
</Window>

BindingExpression error when binding to DependencyProperty WPF

I have a problem when I try to bind a int value to a DependencyProperty in a custom control from a style.
MyClassVM contains an int named Number. It shows perfectly in the Label, when I bind in the same way, but will not set on my custom control. If I change from "{Binding Number}" To "15" for example, everything works great also on the custom control.
<Style TargetType="{x:Type ItemsControl}" x:Key="TestKey">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Style.Resources>
<DataTemplate DataType="{x:Type local:MyClassVM}">
<StackPanel Orientation="Horizontal">
<StackPanel.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding DataContext.OpenProjectCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding .}" />
</StackPanel.InputBindings>
<ctrl:MyCustomControl Margin="5" Width="50" ctrl:MyCustomControl.ValueProperty="{Binding Number}"/>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Number}" FontSize="14" Foreground="{DynamicResource CeriseBrush}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Style.Resources>
</Style>
This is how the MyCustomControl-class looks.
public partial class MyCustomControl: CustomUserControl
{
public int Value { get { return _value; } set { _value = value; } }
public MyCustomControl()
{
InitializeComponent();
DataContext = this;
}
public string ValueProperty
{
get { return (string)GetValue(ValuePropertyProperty); }
set { SetValue(ValuePropertyProperty, value); }
}
public static readonly DependencyProperty ValuePropertyProperty =
DependencyProperty.Register("ValueProperty", typeof(int), typeof(MyCustomControl), new UIPropertyMetadata(ValuePropertyChangedHandler));
public static void ValuePropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
((MyCustomControl)sender).Value = (int)e.NewValue;
}
}
The error I get looks like this:
System.Windows.Data Error: 40 : BindingExpression path error: 'Number' property not found on 'object' ''MyCustomControl' (Name='')'. BindingExpression:Path=Number; DataItem='MyCustomControl' (Name=''); target element is 'MyCustomControl' (Name=''); target property is 'ValueProperty' (type 'Int32')
Not sure, but I think you have to provide a relative source in your binding like:
Label Content="{Binding Number, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
Replace Window with UserControl if you're in a Control

WPF ComboBox selection change after switching tabs

I made a project based on nested tabs.
the nested tabs are different instance of the same viemModel and the same UI.
when I switch between the tabs he comboboxes present in the tabs chenge thei selection depending on the tab that is loosing focus.
I add both the viewmodels and the view of my test project.
thank you in advance for your help
main window
<Window.Resources>
<DataTemplate DataType="{x:Type local:IntermediateViewModel}">
<local:IntermediateView />
</DataTemplate>
<DataTemplate x:Key="HeaderedTabItemTemplate">
<Grid>
<ContentPresenter
Content="{Binding Path=Header, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" >
</ContentPresenter>
</Grid>
</DataTemplate>
<Style x:Key="SimpleTabItemStyle" TargetType="TabItem">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderThickness="1" BorderBrush="#555959">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center"
ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True" Height ="40" MinWidth ="90"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#555959" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="DefaultTabControlTemplate">
<TabControl IsSynchronizedWithCurrentItem="True"
BorderThickness="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource HeaderedTabItemTemplate}"
ItemContainerStyle="{StaticResource SimpleTabItemStyle}"
SelectionChanged="TabControl_SelectionChanged"
/>
</DataTemplate>
<!---->
</Window.Resources>
<Grid MinHeight="200" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="260*" />
<RowDefinition Height="51*" />
</Grid.RowDefinitions>
<Border >
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{DynamicResource DefaultTabControlTemplate}"
/>
</Border>
<Button Grid.Row="1" Content="Add" Command="{Binding AddCommand}"/>
</Grid>
view model (create a different istance each time)
class MainWindowViewModel : WorkspacesViewModel<IntermediateViewModel>
{
public MainWindowViewModel()
{
this.WorkspacesView.CurrentChanged += new EventHandler(WorkspacesView_CurrentChanged);
}
void WorkspacesView_CurrentChanged(object sender, EventArgs e)
{
}
RelayCommand myVar = null;
public ICommand AddCommand
{
get
{
return myVar ?? (myVar = new RelayCommand(param =>
{
SetWindow(new IntermediateViewModel("AA" + this.Workspaces.Count) );
}));
}
}
first level tab
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ClassViewModel}">
<local:ClassView />
</DataTemplate>
</UserControl.Resources>
<Border>
<ContentControl Content="{Binding Path=CurrentWorkspace, Mode=OneWay}" Loaded="ContentControl_Loaded" DataContextChanged="ContentControl_DataContextChanged" IsVisibleChanged="ContentControl_IsVisibleChanged" LayoutUpdated="ContentControl_LayoutUpdated" TargetUpdated="ContentControl_TargetUpdated" Unloaded="ContentControl_Unloaded" />
</Border>
first level viewmodel
class IntermediateViewModel : WorkspacesViewModel
{
public string Header { get; set; }
public IntermediateViewModel(string header)
{
Header = header;
SetWindow(new ClassViewModel(header));
}
}
nested tab
<UserControl.Resources>
<CollectionViewSource x:Key="StatusView" Source="{Binding Path=StatusList}"/>
</UserControl.Resources>
<Grid>
<ComboBox Name="_spl2Status" ItemsSource="{Binding Source={StaticResource StatusView}}"
SelectedValue="{Binding Path=MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="FL_TYPE"
DisplayMemberPath="ID_TYPE" Margin="76,12,0,0" Height="40" VerticalAlignment="Top" HorizontalAlignment="Left" Width="146"
DataContextChanged="_spl2Status_DataContextChanged"
IsVisibleChanged="_spl2Status_IsVisibleChanged"
Loaded="_spl2Status_Loaded"
SelectionChanged="_spl2Status_SelectionChanged"
>
</ComboBox>
</Grid>
nested tab view model
public enum myTypes
{
tipo0 = 0,
tipo1 = 1,
tipo2 = 2,
}
class ClassViewModel : WorkspaceViewModel
{
public ClassViewModel(string name)
{
Name = name;
}
public string Name { get; set; }
private List<IntEnumType> _statusList = null;
public List<IntEnumType> StatusList
{
get
{
if (_statusList == null)
_statusList = new List<IntEnumType>()
{
new IntEnumType((int)myTypes.tipo0, myTypes.tipo0.ToString()),
new IntEnumType((int)myTypes.tipo1, myTypes.tipo1.ToString()),
new IntEnumType((int)myTypes.tipo2, myTypes.tipo2.ToString()),
};
return _statusList;
}
}
private int myVar = 1;
public int MyProperty
{
get
{
return myVar;
}
set
{
if (myVar != value)
{
myVar = value;
OnPropertyChanged(() => MyProperty);
}
}
}
}
public class TabItemStyleSelector : StyleSelector
{
public Style MainTabItem { get; set; }
public Style ChildrenTabItem { get; set; }
public Style SpecificationTabItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
//if (item is IHome)
// return MainTabItem;
//else if (item is SpecificationItemViewModel)
// return SpecificationTabItem;
//else
return ChildrenTabItem;
}
}
The code is a little hard to completely follow, but I'm guessing that the issue is that there is only one instance of your ClassViewModel and it is where the selection for the combo box is stored {Binding Path=MyProperty, so whatever is stored in MyProperty will be reflected in all instances of the combo box regardless of where they live.
Well this is a bit late, but as I'm facing the same issue, I want to share my analysis.
When you change your tabs, you change the DataContext of the current Tab to your other ViewModel and hence also the ItemsSource of your ComboBox.
In case your previously selected Item (SelectedItem) is not contained within the new ItemsSource, the ComboBox fires a SelectionChanged-Event and therefore sets the SelectedIndex to -1.
Altough this default behaviour of the ComboBox might make sense, it's very annoying in many cases.
We've derived an own class from ComboBox, handling that. But it's not very satisfying as you loose some default behaviour you most probably need.
The problem is in your loaded event handlers.
When you switch tabs your unloading one tab and loading a new one.
I suspect your changing MyComboBox.SelectedIndex in _spl2Status_Loaded.

DependencyProperty in my UserControl fails to update bound property in ViewModel

I have made an usercontrol that contains a TextBox with some custom behaviours and I want to bind the Text property to a property in my ViewModel.
I have isolated the problem into a sample solution and manage to update the Text property with the ViewModel property value, but when I write into the textbox and leaves the textbox my Person.Name property is not updated.
My usercontrol xaml:
<UserControl x:Class="WpfCustomUserControlBinding.TextBoxReadOnlyLooksDisabled"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Control.Resources>
<Style x:Key="readOnlyTextbox">
<Style.Triggers>
<Trigger Property="TextBoxBase.IsReadOnly" Value="True">
<Setter Property="TextBoxBase.Background" Value="WhiteSmoke" />
<Setter Property="TextBoxBase.Foreground" Value="#FF6D6D6D" />
<Setter Property="TextBox.BorderBrush" Value="DarkGray" />
<Setter Property="TextBoxBase.BorderThickness" Value="1,1,1,1" />
</Trigger>
<Trigger Property="TextBoxBase.IsReadOnly" Value="False">
<Setter Property="TextBoxBase.Background" Value="White" />
<Setter Property="TextBoxBase.Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</Control.Resources>
<TextBox Style="{StaticResource readOnlyTextbox}" x:Name="txtTextBoxBase" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
The codebehind code:
public partial class TextBoxReadOnlyLooksDisabled
{
public TextBoxReadOnlyLooksDisabled()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof (string)
, typeof (TextBoxReadOnlyLooksDisabled)
,new PropertyMetadata(OnTextChange));
private static void OnTextChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBoxReadOnlyLooksDisabled = (TextBoxReadOnlyLooksDisabled) d;
textBoxReadOnlyLooksDisabled.txtTextBoxBase.Text = (string) e.NewValue;
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Window where I try to get the sample to work:
<Window x:Class="WpfCustomUserControlBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfCustomUserControlBinding" Title="MainWindow" Height="153" Width="525">
<Window.Resources>
<src:Person x:Key="myDataSource"/>
</Window.Resources>
<Grid >
<Label Content="Plain vanilla" Height="26" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" Width="143" />
<Label Content="Messed up version" Height="26" HorizontalAlignment="Left" Margin="12,61,0,0" Name="label2" VerticalAlignment="Top" Width="143" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="152,15,0,0" x:Name="txtVanlig" VerticalAlignment="Top" Width="251" Text="{Binding Source={StaticResource myDataSource}, Path=Name, Mode=TwoWay}"/>
<src:TextBoxReadOnlyLooksDisabled Height="23" HorizontalAlignment="Left" Margin="152,61,0,0" x:Name="txtVrien" VerticalAlignment="Top" Width="251" Text="{Binding Source={StaticResource myDataSource}, Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
The sample value class:
public class Person
{
private string _name = "King Chaos";
public string Name{get{return _name;}set{_name = value;}}
}
Thanks in advance. ;)
Edit: Adding INotifyPropertyChanged does not do the trick since the set method of the Name is not accessed when updating my custom TextBox.
The problem is that the TextBox inside your TextBoxReadOnlyLooksDisabled UserControl has no two-way binding to the Text property - you only update the TextBox programmatically (in the OnTextChanged handler) when your property value changes, but not vice-versa.
Why not just drop the changed handler altogether, and add a binding instead, like this:
<UserControl x:Class="WpfCustomUserControlBinding.TextBoxReadOnlyLooksDisabled"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Control.Resources>
//...
</Control.Resources>
<TextBox Style="{StaticResource readOnlyTextbox}"
x:Name="txtTextBoxBase"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="{Binding Path=Text, Mode=TwoWay}"/>
Don't forget to also set the DataContext accordingly:
public partial class TextBoxReadOnlyLooksDisabled : UserControl
{
public TextBoxReadOnlyLooksDisabled()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string),
typeof(TextBoxReadOnlyLooksDisabled));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Well the problem you are experiencing is caused because the Text dependency property of the TextBox inside of your custom TextBoxReadOnlyLooksDisabled is actually not bound to your "ViewModel" (the Person class) and so when you write something in that txtTextBoxBase its Text dp is changed, but the change is not propagated back to the ViewModel.
What you can do is wire the Text dp of the nested TextBox to the Text dp of your custom control with:
<TextBox x:Name="txtTextBoxBase"
Text={Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBoxReadOnlyLooksDisabled}}} />
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name = "King Chaos";
public string Name{
get{
return _name;
}
set{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs("Name"));
}
}
}
Simply your model must Implement INotifyPropertyChanged and raise property changed whenever your property is set, so that XAML will detect a change and refresh its value.

Categories