Bind a property in xaml template to viewmodel (RibbonTextBox) - c#

Background info of larger problem
The problem I am trying to solve is to allow a user to set the MinWidth of the label inside of the RibbonTextBox control template. I intend to the same with other properties once I can figure out the first one. The aim of this is to be able to align RibbonTextBoxes stacked on top of each other by setting widths. I am so far solved my problem by hardcoding the values in the control template. I would like to make this control reusable and thus need to be able to set up some binding.
The problem that needs solving
I have the following xaml (lots of xaml has been removed for readability). At the centre of this xaml you can see a label. That label has a MinWidth property which is the focus of my question.
<DataTemplate x:Uid="DataTemplate_0" DataType="{x:Type element:RibbonTextBoxVM}">
<ribbon:RibbonTextBox x:Uid="ribbon:RibbonTextBox_1" IsReadOnly="{Binding IsReadOnly}" Text="{Binding Text}" Label="{Binding Label}" >
<ribbon:RibbonTextBox.Style>
<Style TargetType="{x:Type ribbon:RibbonTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ribbon:RibbonTextBox}">
<StackPanel Orientation="Horizontal">
<Label Margin='2,0,0,0' Padding='0,0,0,5' BorderThickness='0,0,0,0' HorizontalAlignment='Stretch' VerticalAlignment='Bottom'
HorizontalContentAlignment='Left' VerticalContentAlignment='Top' Background='#00FFFFFF' FlowDirection='LeftToRight'
Visibility='Visible' MinWidth="80">
<!--other stuff-->
</Label>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ribbon:RibbonTextBox.Style>
</ribbon:RibbonTextBox>
</DataTemplate>
The following is the viewmodel that backs the above xaml.
public class RibbonTextBoxVM : ViewModel
{
public string Label
{
get { return GetValue(Properties.Label); }
set { SetValue(Properties.Label, value); }
}
public string Text
{
get { return GetValue(Properties.Text); }
set { SetValue(Properties.Text, value); }
}
public bool IsReadOnly
{
get { return GetValue(Properties.IsReadOnly); }
set { SetValue(Properties.IsReadOnly, value); }
}
public RibbonTextBoxVM(string text, string label, bool isReadOnly)
{
Text = text;
Label = label;
IsReadOnly = isReadOnly;
}
}
What I would like to do is have a property LabelMinWidth.
public double LabelMinWidth
{
get { return GetValue(Properties.LabelMinWidth); }
set { SetValue(Properties.LabelMinWidth, value); }
}
I want to allow the user to pass in a value to the constructor to set that property. That is the easy part.
The part I cannot figure out is how to bind my new LabelMinWidth to the MinWidth property of the label inside the control template in the xaml.
If anyone can point me in the right direction that would be great. Ill be happy to answer any questions about the problem.

Since your In your RibbonTextBox has your VM as its DataContext, you can use a Bindingin your ControlTemplate, just like you bound the other properties:
<Label ... MinWidth="{Binding LabelMinWidth}">
This works because in WPF, the DataContext inherits to all children (unless overridden). So if you have a property on your VM that you want to bind to in a control in a template, you just bind to it.

Related

Proper way to create Custom Bindable WPF Control

I want to ask about the right way if I want to create Bindable user control consisting of two controls. I am not sure about what I am doing - whether I do it correctly , because I run into some problems.
Here is what I am trying to do:
Lets call this control ucFlagControl . Create new , custom user control ...
Its purpose is to show Color interpretation of logic ( True/ False ) value in variable , type of Bool.
What I used to do before was that I use Rectangle, and Bind FillProperty to boolean value using Converter
What I did to make it works was , that I made a usercontrol , and put rectangle and label inside
than I added this code:
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
OnPropertyChanged("LabelContent");
}
}
///in case that I use integer or array
public int BitIndex
{
get { return (int)GetValue(BitIndexProperty); }
set
{
SetValue(BitIndexProperty, value);
OnPropertyChanged("BitIndex");
}
}
public string BindingSource
{
get { return (string)GetValue(BindingSourceProperty); }
set
{
SetValue(BindingSourceProperty, value);
OnPropertyChanged("BindingSource");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
public static readonly DependencyProperty BitIndexProperty =
DependencyProperty.Register("BitIndex", typeof(int), typeof(ucStatusFlag), new PropertyMetadata(0));
public static readonly DependencyProperty BindingSourceProperty =
DependencyProperty.Register("(BindingSource", typeof(string), typeof(ucStatusFlag), new PropertyMetadata(""));
private void StatusFlag_Loaded(object sender, RoutedEventArgs e)
{
if (BindingSource.Length > 0)
{
Binding bind = new Binding();
string s = LabelContent;
int i = BitIndex;
bind.Converter = new StatusToColor();
bind.Path = new PropertyPath(BindingSource);
bind.ConverterParameter = BitIndex.ToString();
bind.Mode = BindingMode.OneWay;
bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
recStatusBit.SetBinding(Rectangle.FillProperty, bind);
}
}
private class StatusToColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
byte bDataWordIdx;
byte bDataBitIdx;
Byte.TryParse((string)parameter, out bDataBitIdx);
if (Object.ReferenceEquals(typeof(UInt16[]), value.GetType()))
{
UInt16[] uiaData = (UInt16[])value;
bDataWordIdx = (byte)uiaData[0];
if ((uiaData[bDataBitIdx / 16] >> (bDataBitIdx % 16) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
else if (Object.ReferenceEquals(typeof(UInt16), value.GetType()))
{
UInt16 uiaData = (UInt16)value;
if (((uiaData >> bDataBitIdx) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return 0;
}
}
}
}
Than I realized that I can easily bind content and I do not have to create public static readonly DependencyProperty LabelContentProperty
but just property
public new string Content
{
get { return (string)label.Content; }
set
{
SetValue(label.Content, value);
OnPropertyChanged("Content");
}
}
this overrides the original content so I am able to Bind and/or assign the text of the label in upper level - in e.g. MainWindow.xaml where this user control is put
First question is if this is in this case OK or if there is some background I am not aware of and I should even such small controls do in different way - I would like to make dll. from it an load it to toolbox - I tested it works. And than use it in for example stack panel .
Second question is that I have problem with a rectangle "Fill" property . I am not able to bind that property like I bind content .
I know that the rectangle is derived from Shape class so I am not sure if it has something to do with this.
If I am able to do the inner binding or connection same as in
Content
I can remove the converters than and just bind it in e.g. MainWindow.xaml file (using the converter and converter parameter )
But FillProperty does not work for me so I am not sure about my point of view .
Thank you for suggestions
EDIT:
well I am sorry but I did not catch all you want to say in a comment below. Could you please explain closer ?
I know that the code above is not the right way to do it ... ?
Or can you post any article about it ?
my actual code is like this:
In a user control ... I removed all the code from code behind ...
' <Label x:Name="lStatusBit" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="2,1,17,2" />
<Rectangle x:Name="recStatusBit" Margin="0,3,1,7" />'
Content property works, I cant see Rectangle , and rectangle fill property ...
Other problem is if I fill in Content property in XAML where my uc is placed , Rectangle disappears .
I know I'm a year late to the party, but I'll answer incase anyone else comes across this.
My Suggestions
You should use a TextBlock control instead of Label controls if you want to display pure text. Labels have a content element which is re-rendered/computed many more times than a TextBlock's simple Text property.
You should avoid using magic strings, e.g. "LabelContent". You should use the C# nameof() expression when referencing property names. For example:
I use lambda expressions to clean up the code a bit, but this is just preference.
public string LabelContent
{
get => (string)GetValue(LabelContentProperty);
set => SetValue(LabelContentProperty, value);
}
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register(
nameof(LabelContent),
typeof(string),
typeof(ucStatusFlag),
new PropertyMetadata("Default Value"));
This will prevent runtime errors due to mistyped text, will allow you to jump to the property's reference, will make refactoring easier, and will make debugging easier by giving you a compile error that's easy to find (if the property doesn't exist).
I don't think you need the rectangle. If you're just trying to change the background color of the text area you can use a DataTrigger or make a converter.
DataTrigger Example
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<!-- The default value -->
<Setter Property="Background" Value="Transparent" />
<!-- Your trigger -->
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBooleanValue}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
A DataTrigger is a quick and easy way to style a control by binding to a property on your ViewModel (assuming you're using the MVVM structure), but there are some cons - like reusing the same style on a different View whose ViewModel's properties are different. You'd have to rewrite the entire styling again.
Lets turn it into a reusable control where we can (1) specify a highlight background color, and (2) use a boolean to determine whether the control is highlighted.
Template Controls vs UserControls
I make my templated controls in a separate C# class file and put the control's styling in another separate resource dictionary file instead of using a UserControl.
These templated controls can consist of several other controls to make a single reusable control.
It's my understanding that UserControls are meant to use multiple templated controls (e.g. a TextBox) and link their interactions together to perform a specific way.
I don't think these controls are meant to be reusable in separate unrelated projects - they display data depending on your ViewModel which can be situational.
If you want to extend your custom control in the future via inheritance, then using a UserControl will make things difficult.
Here's what a few of my controls look like in the solution explorer:
Solution Files Snippet
The ExpansionPanel control in the snippet is an Expander with additional functionalities/properties.
The NavButton is a Button with additional functionalities/properties also.
I have a NavigationView UserControl that uses both of those controls to create something much larger than a templated control.
It sounds like you want to create a reusable templated control.
Creating a Custom Control
Here are the basic steps:
Create a "Themes" folder at the root of your project. It must be at the root of your project and spelling does matters.
Create a Generic.xaml Resource Dictionary file in the "Themes" folder. It must be directly under the "Themes" folder and spelling does matters.
This is where you store the default themes for your custom controls.
The template style for your control will automatically be added to the Generic.xaml file when you add a Custom Control template to your project.
<Style TargetType="{x:Type local:Example}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Example}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Personally, I like to have separate .xaml file for each control, and then I merge it into the Generic.xaml resource dictionary. This is just for organization purposes.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- Control template styles -->
<ResourceDictionary Source="pack://application:,,,/Themes/ExpansionPanel.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/NavButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocument.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocumentToolBar.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextEditor.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/HighlightTextBlock.xaml" />
<!-- etc... -->
</ResourceDictionary.MergedDictionaries>
<!-- Other styles or whatever -->
</ResourceDictionary>
It's important to note that order does matter if you have controls that depend on other controls.
Merge the Generic.xaml file into your App.xaml file.
<Application>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
<ResourceDictionary Source="pack://application:,,,/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
</ResourceDictionary>
</Application.Resources>
</Application>
Why not just merge the control templates in the App.xaml file directly? WPF looks directly for the Generic.xaml file for custom type themes. App.xaml is also application specific and wouldn't be able to be usable in other applications if you used the library as a control library.
Create a .cs file using the built in Custom Control template OR a standard C# class file.
Your control's .cs file would resemble something similar to...
public class HighlightTextBlock : Control
{
#region Private Properties
// The default brush color to resort back to
public Brush DefaultBackground;
#endregion
static HighlightTextBlock()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(typeof(HighlightTextBlock)));
}
// Get the default background color and set it.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
DefaultBackground = Background;
}
#region Dependency Properties
/// <summary>
/// The text to display.
/// </summary>
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(string.Empty));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>
/// Whether or not the background should be highlighted.
/// </summary>
// This uses a callback to update the background color whenever the value changes
public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
nameof(Highlight), typeof(bool),
typeof(HighlightTextBlock), new PropertyMetadata(false, HighlightPropertyChangedCallback));
public bool Highlight
{
get => (bool)GetValue(HighlightProperty);
set => SetValue(HighlightProperty, value);
}
/// <summary>
/// The highlight background color when <see cref="Highlight"/> is true.
/// </summary>
public static readonly DependencyProperty HighlightColorProperty = DependencyProperty.Register(
nameof(HighlightColor), typeof(Brush),
typeof(HighlightTextBlock), new PropertyMetadata(null));
public Brush HighlightColor
{
get => (Brush)GetValue(HighlightColorProperty);
set => SetValue(HighlightColorProperty, value);
}
#endregion
#region Callbacks
// This is the callback that will update the background
private static void HighlightPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var target = (HighlightTextBlock)dependencyObject;
if (target.Highlight)
target.Background = target.HighlightColor;
else
target.Background = target.DefaultBackground;
}
#endregion
}
Create a ResourceDictionary.xaml file to store your control's template and style OR add it directly in Generic.xaml.
Your .xaml file would look something like...
<Style x:Key="HighlightTextBlock" TargetType="{x:Type ctrl:HighlightTextBlock}">
<!-- Default setters... -->
<!-- Define your control's design template -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:HighlightTextBlock}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<!--
I only bound the Text and Background property in this example
Make sure to bind other properties too.. like Visibility, IsEnabled, etc..
-->
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
Set the default style for the control
The above style has a key, so controls won't use that style
unless the style is explicitly set.
e.g.
<ctrl:HighlightTextBlock Style={StaticResource HighlightTextBlock} />
The reason I used a key above is to allow extending/reusing that default style.
If a key wasn't present then you wouldn't be able to reference it in
another style.
-->
<Style TargetType="{x:Type ctrl:HighlightTextBlock}" BasedOn="{StaticResource HighlightTextBlock}" />
Add a reference to the control's resource dictionary in Generic.xaml, like in step 2's code snippet.
Usage:
I'm binding the IsChecked property to a IsHighlighted property on my ViewModel.
You can bind it to whatever.
<StackPanel>
<ToggleButton IsChecked="{Binding IsHighlighted}" Content="{Binding IsHighlighted}"
Width="100" Height="35" Margin="5"/>
<ctrl:HighlightTextBlock Background="Transparent" HighlightColor="Red"
Text="HELLO WORLD!!!" Highlight="{Binding IsHighlighted}"
Width="100" Height="35" HorizontalAlignment="Center" />
</StackPanel>
On False Snippet
On True Snippet
Your controls may look a bit different - I'm using a custom dark theme.
Finally I found this working :
XAML
<UserControl x:Class="ucStatusFlag"
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"
d:DesignHeight="17" d:DesignWidth="100"
x:Name="StatusFlag">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="recStatusBit" Grid.Column="0" Stroke="Black" Width="11" Fill="{Binding ElementName=StatusFlag, Path=RectangleColor}" Margin="0,2,0.2,3.8" />
<Label Height="17" x:Name="lStatusBit" Foreground="Black" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="5,0,0,1" Content="{Binding ElementName=StatusFlag, Path=LabelContent}" />
</Grid>
C#
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
}
}
public SolidColorBrush RectangleColor
{
get { return (SolidColorBrush)GetValue(RectangleColorProperty); }
set
{
SetValue(RectangleColorProperty, value);
}
}
public static readonly DependencyProperty RectangleColorProperty =
DependencyProperty.Register("RectangleColor", typeof(SolidColorBrush), typeof(ucStatusFlag), new PropertyMetadata(Brushes.Gold));
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
}
Binding in Another Project :
<ucStatusFlag HorizontalAlignment="Left" Height="18" Margin="154,224,0,0" VerticalAlignment="Top" Width="100" LabelContent="ABC" RectangleColor="{Binding RectangleColorPropertyInProject}"/>
Where RectangleColorPropertyInProject is Property In certain project view model

MVVM Basic ContentControl Binding

I'm trying to figure out the correct way to bind a ViewModel to a ContentControl (I've looked all over the net but can't find an example that I can get to work correctly).
My Model:
public class Model
{
private string _Variable = "TEST";
public string Variable
{
get { return _Variable; }
set { _Variable = value; }
}
}
My ViewModel
public class ViewModel :ViewModelBase
{
private Model _Model = new Model();
public string Variable
{
get { return _Model.Variable; }
set
{
if (_Model.Variable != value)
{
_Model.Variable = value;
RaisePropertyChanged("Variable");
}
}
}
My View/Window
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBox/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding Path=Variable}" />
</StackPanel>
So in essence, I have (or at least I believe I have) set the content of the ContentControl to the ViewModel property 'Variable', it is of type string so the only DataTemplate should be implemented and a Textbox displayed.
And that happens... A Textbox is displayed! However the Textbox is empty, and any changes made do not impact Variable.
This means I have made an error in the Batabinding, but I don't understand where. I have a feeling that just because my DataTemplate is displaying a Textbox, nothing is actually binding the string to it, but that is the bit I'm lost over.
Thanks for any help/advice.
You haven't specified the TextBox's Text binding, which is completely separate to the DataContext. Since you want the TextBox to bind to its own DataContext just do this:
<TextBox Text="{Binding Path=.}"/>
use textbox as below:
<TextBox Text="{Binding}" />

Set TextBlock Visibility based on a bound bool

I am trying to link the visibility of a TextBlock to a bool property which is also linked to a checkbox using WPF and c#. I have the following code in two different sections of the same xaml file (one section is a summary, and the other is settings. I am very new to WPF, and am learning as I go. Currently, the TextBlock is visible no matter what the value of IsSecondaryMessageFilePath is.
<TextBlock Name="secondaryfolderinfo" Foreground="Red">
<ContentControl Content="Secondary message folder" Foreground ="Black" />
<ContentControl Content = "{Binding Path=SecondaryMessageFilePath}" ContentStringFormat="" ClipToBounds="False"></ContentControl>
<ContentControl Content = " "></ContentControl>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSecondaryMessageFilePath}" Value="True">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Further down I have:
<CheckBox IsChecked="{Binding Path=IsSecondaryMessageFilePath, Mode=TwoWay}"
Name="SecondaryPathCheckBox"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="320,7,0,0">Save additional locations</CheckBox>
Finally, in the code-behind, I have:
public bool IsSecondaryMessageFilePath
{
get { return _isSecondaryMessageFilePath; }
set
{
if (_isSecondaryMessageFilePath != value)
{
_isSecondaryMessageFilePath = value;
OnPropertyChanged("IsSecondaryMessageFilePath");
}
}
}
private bool _isSecondaryMessageFilePath;
public string SecondaryMessageFilePath
{
get { return _secondaryMessageFilePath; }
set
{
if (_secondaryMessageFilePath != value)
{
_secondaryMessageFilePath = value;
OnPropertyChanged("SecondaryMessageFilePath");
}
}
}
private string _secondaryMessageFilePath;
Any assistance would be appreciated.
EDIT
Working from the suggestion below, I tried adding the BooleanToVisibilityConverter, but am getting a missing assembly reference for it, and am to new to WPF to figure out how to resolve it. My opening code is as follows:
<UserControl x:Class="Sender_Receiver.SenderReceiverSetup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:m=...
xmlns:
<UserControl.Resources>
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
...
Your code looks ok at first glance, but you really don't need to use a data trigger for this. WPF comes with a BooleanToVisibilityConverter class that you declare in your resources:
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
Then in your TextBlock, you bind Visibility:
<TextBlock Visibility="{Binding Path=IsSecondaryMessageFilePath, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Just so you know, there may be a simpler way to do this, just bind to the IsChecked property itself!
<CheckBox x:Name="UseSecondaryPath"/>
<TextBlock Visibility="{Binding ElementName=UseSecondaryPath, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Of course if you need the bool for something else that wouldn't be an ideal solution, but it is a little cleaner if its just for the UI.
The code for a custom BooleanToVisibilityConverter, if you are interested, is:
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert (object value, ...)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, ...)
{
return Binding.DoNothing;
}
}
Let me know if I can clarify anything or assist further.
private Boolean _IsChecked;
//Bind this to your checkbox
public Boolean IsChecked
{
get { return _IsChecked; }
set { _IsChecked= value; OnPropertyChanged("IsChecked"); OnPropertyChanged("TextBoxVis"); }
}
//Bind this to your TextBox's Visibility Property
public Visibility TextBoxVis
{
get { return IsChecked ? Visibility.Visible : Visibility.Collapsed; }
}

How to disable a button if no items are selected in a ListView

I have a ListView Contained in a UserControl I would like to disabled a button when no items are selected in the UserControl, would it be the right way to do it? So far, it doesn't disable, it just stays enable all the way.
I've included the xaml code.
searchAccountUserControl is the UserControl name property in the xaml.
And AccountListView is the ListView name property in the userControl xaml.
<Button Content="Debit" IsEnabled="true" HorizontalAlignment="Left" Margin="18,175,0,0" Name="DebitButton" Width="128" Grid.Column="1" Height="32" VerticalAlignment="Top" Click="DebitButton_Click">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=searchAccountUserControl.AccountListView, Path=SelectedValue}" Value="{x:Null}" >
<Setter Property="Button.IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Thanks.
Finally i've used :
in my ViewModel :
private bool _isSelected;
public bool IsSelected { get { return _isSelected; }
set { _isSelected = _account.View.CurrentItem != null;
PropertyChanged.SetPropertyAndRaiseEvent(this, ref _isSelected, value,
ReflectionUtility.GetPropertyName(() => IsSelected)); } }
And then Use isEnabled = "{Binding Path=IsSelected}" in the xaml.
There are a few things wrong here.
Precedence, if you set IsEnabled on the control itself the style will never be able to change it.
ElementName, it's an ElementName, not a path, just one string that gives the name of one element. Everything beyond that goes into the Path.
Style syntax, if you set a Style.TargetType you should not set the Setter.Property with a type prefix (although leaving it does not break the setter).
By the way, this alone is enough:
<Button IsEnabled="{Binding SelectedItems.Count, ElementName=lv}" ...
It's obvious that you aren't using Commanding (ICommand Interface). You should either use that (and preferably the Model-View-ViewModel architecture).
But, if you want to stick with code-behind and XAML:
<ListView SelectionChanged="AccountListView_SelectionChanged" ... />
private void AccountListView_SelectionChanged(Object sender, SelectionChangedEventArgs args)
{
DebitButton.IsEnabled = (sender != null);
//etc ...
}
More information on MVVM: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
You need to set the DataContext of the View (UserControl) to the instance of the ViewModel you want to use. Then, from there, you can bind to properties on the ViewModel, including ICommands. You can either use RelayCommand (see link above) or use Commanding provided by a framework (for example, Prism provides a DelegateCommand). These commands take an Action (Execute) and a Func (CanExecute). Simply provide the logic in your CanExecute. Of course, you'd also have to have your ListView SelectedItem (or SelectedValue) be databound to a property on your ViewModel so you can check to see if it's null within your CanExecute function.
Assuming you use RelayCommand you don't have to explicitly call the RaiseCanExecuteChanged of an ICommand.
public class MyViewModel : ViewModelBase //Implements INotifyPropertyChanged
{
public MyViewModel()
{
DoSomethingCommand = new RelayCommand(DoSomething, CanDoSomething);
}
public ObservableCollection<Object> MyItems { get; set; }
public Object SelectedItem { get; set; }
public RelayCommand DoSomethingCommand { get; set; }
public void DoSomething() { }
public Boolean CanDoSomething() { return (SelectedItem != null); }
}
<ListView ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem}" ... />
<Button Command="{Binding DoSomethingCommand}" ... />

Attach ICommand in WPF UserControl

I implemented a simple button with an image in it:
<Button Command="{Binding ButtonCommand, ElementName=ImageButtonControl}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ButtonImage, ElementName=ImageButtonControl}"/>
<TextBlock Text="{Binding ButtonText, ElementName=ImageButtonControl}" Margin="2,0,0,0"/>
</StackPanel>
</Button>
As you can see, I expose a ButtonCommand property in order to be able to attach an ICommand to this UserControl:
public partial class ImageButton : UserControl
{
/// <summary>
/// The dependency property that gets or sets the source of the image to render.
/// </summary>
public static DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(ImageButton));
public static DependencyProperty TextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(ImageButton));
public static DependencyProperty ButtonCommandProperty =
DependencyProperty.Register("ButtonCommand", typeof(ICommand), typeof(ImageButton));
public ImageButton()
{
this.DataContext = this;
InitializeComponent();
}
/// <summary>
/// Gets or sets the button command.
/// </summary>
public ICommand ButtonCommand
{
get { return (ICommand)GetValue(ImageButton.ButtonCommandProperty); }
set { SetValue(ImageButton.ButtonCommandProperty, value); }
}
/// <summary>
/// Gets or sets the button image.
/// </summary>
public ImageSource ButtonImage
{
get { return (ImageSource)GetValue(ImageButton.ImageSourceProperty); }
set { SetValue(ImageButton.ImageSourceProperty, value); }
}
/// <summary>
/// Gets or sets the button text.
/// </summary>
public string ButtonText
{
get { return (string)GetValue(ImageButton.TextProperty); }
set { SetValue(ImageButton.TextProperty, value); }
}
}
Then when I declare my button it gives this:
<uc:ImageButton Grid.Row="1" Grid.Column="0" ButtonCommand="{Binding AttachContextCommand}" ButtonImage="{StaticResource AssociateImage}" ButtonText="Associer"/>
And badaboom, nothing never happen when I click on my ImageButton.
When I replace the ImageButton with a simple button, the ICommand is called.
I even tried to simply extends the Button class and bind an ICommand, but once again, it didn't work...
Help appreciated !
Thx.
You can achieve this in a much cleaner way using a style and a couple of attached properties.
The attached properties will store your specific information.
The style will use these properties and build the look you want.
The element will still be a button so the command and everything else will work.
public class ImageButton
{
public static ImageSource GetImage(DependencyObject obj)
{
return (ImageSource)obj.GetValue(ImageProperty);
}
public static void SetImage(DependencyObject obj, ImageSource value)
{
obj.SetValue(ImageProperty, value);
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));
public static String GetCaption(DependencyObject obj)
{
return (String)obj.GetValue(CaptionProperty);
}
public static void SetCaption(DependencyObject obj, String value)
{
obj.SetValue(CaptionProperty, value);
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.RegisterAttached("Caption", typeof(String), typeof(ImageButton), new UIPropertyMetadata(null));
}
<Style TargetType="{x:Type Button}"
x:Key="ImageButton">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=(local:ImageButton.Image), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
<TextBlock Text="{Binding Path=(local:ImageButton.Caption), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Margin="2,0,0,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
You can then use this to declare buttons:
<Button Style="{DynamicResource ImageButton}"
local:ImageButton.Caption="Foo"
local:ImageButton.Image="..." />
Note:
I'm pretty sure it would be cleaner to go through the "Template" property and use a ControlTemplate and TemplateBindings, but that would mean re-creating the border and other stuff around your content, so if you are looking to just define a default "Content", my example would be the way to go, I think.
If the only added functionality that you want for your button is to have an image on it, then I think you're approaching this from the wrong direction. WPF is as awesome as it is because the UI controls are look-less. This means that a Control is merely a definition of functionality + some template to define how it looks. This means that the template can be swapped out at any time to change the look. Also, almost any content can be placed inside of almost any control
For instance, to define a button in your xaml that has the look your going for all you need is this:
<Window ...>
...
<Button Command="{Binding AttachContextCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{StaticResource AssociateImage}"/>
<TextBlock Text="Associer"/>
</StackPanel>
</Button>
...
</Window>
Just keep in mind that with WPF you don't have to define a new CustomControl or UserControl every time you want to change the look and feel of something. The only time you should need a CustomControl is if you want to add functionality to an existing Control or to create functionality that doesn't exist in any other Control.
Edit Due to comment:
If you're wanting to keep from defining the content for the button every time, the other option is to just have a poco (plain old CLR object) class that would define everything your interested in (I'll write my example as if you're doing this for a tool bar, because it makes sense to me):
public class ToolBarItem : INotifyPropertyChanged
{
public string Text { get ... set ... }
public ICommand Command { get ... set ... }
public ImageSource Image { get ... set ... }
}
That has a data template defined somewhere (App.xaml, Window.Resources, etc):
<DataTemplate DataType="{x:Type l:ToolBarItem}">
<Button Command="{Binding Command}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</Button>
</DataTemplate>
And then use the guy in your xaml like this:
<Window ...>
...
<ContentControl>
<ContentControl.Content>
<l:ToolBarItem Image="..." Command="..." Text="..."/>
</ContentControl.Content>
</ContentControl>
...
</Window>
I just don't know that the way you're trying to do it is the most WPF way you could do it.
EDIT Updated based on second comment
Sorry, I forgot to include the ContentControl surrounding that. Now that I remembered that, I realize that that's not much less verbose than the original where you are specifying the content manually. I'll post a new answer to help with your original question.
To re-answer the original question:
What I think you want to do is create a new CustomControl called ImageButton. Then change it to extend from Button instead of Control. You won't need a Command property since Button already has one. You'll only need to add an Image property and you can reuse the Content property from button instead of having a Text property.
When your CustomControl is created, it'll add an entry in your Generic.xaml for the default style of your ImageButton. In the Setter for the Template property you can change the ControlTemplate to this:
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<StackPanel Orientation="Horizontal">
<Image Source="{TemplateBinding Image}"/>
<ContentPresenter/>
</StackPanel>
</ControlTemplate>
Then, again, when you want to use it:
<Window ... >
...
<l:ImageButton Image="{StaticResource ...}" Command="...">
Associer
</l:ImageButton>
...
</Window>
The built-in WPF button contains code that fires the attached command in response to being clicked. Your "ImageButton" derives from UserControl, so you don't get that behavior. Probably the shortest route to get what you want is for your ImageButton class to actually derive from the WPF Button class. To accomplish that, change the markup for ImageButton from
<UserControl
...
>
...
</UserControl>
to
<Button
...
>
...
</Button>
Then change the base class of ImageButton from UserControl to Button.
You'll probably need to make some other minor changes before it all works.

Categories

Resources