I am trying to bind ribbon buttons property IsEnabled to code property so I can make it available or not to the user depending on a certain situation.
I don't know what I am doing wrong and I can't figure it out
Currently I have the following code.
The class ViewModel for the property and the event
Public Class ViewModel
Implements INotifyPropertyChanged
Private _btnStart As Boolean
Public Property btnStartVM() As Boolean
Get
Return _btnStart
End Get
Set(ByVal value As Boolean)
_btnStart = value
NotifyPropertyChanged("btnStartVM")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Public Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Button declaration on the xaml
<RibbonButton x:Name="btnStart" Style="{StaticResource btnTriggers}" SmallImageSource="Images/start.ico" Label="Start"/>
and the Style
<Style x:Key="btnTriggers" TargetType="{x:Type RibbonButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=btnStartVM , ElementName=btnStart}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=btnStartVM, ElementName=btnStart}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
Public Sub Window_Loaded(ByVal sender As System.Object, ByVal e As RoutedEventArgs) Handles MyBase.Loaded
InitializeComponent()
DataContext = New ViewModel()
DataContext.btnStartVM = True
End Sub
The DataContext.btnStartVM = True does the job and it triggers the NotifyPropertyChanged but it dosen`t reflect back to the UI.
Your property is named differently from the property you are reporting in your notification.
The property is named btnStartVM but you are reporting btnStart. Change the call to this:
NotifyPropertyChanged("btnStartVM")
Additionally, the binding looks very off. You should probably remove the , ElementName=btnStart part.
Finally, you don't need to work with triggers here. The following style should work just as well:
<Style x:Key="btnTriggers" TargetType="{x:Type RibbonButton}">
<Setter Property="IsEnabled" Value="{Binding btnStartVM}" />
</Style>
Related
Im trying to create a reusable style for a button with WPF, and i want to pass parameters from .xaml but it's not working even when i insert a breakpoint in the ButtonStyle Class while debugging he don't enter in it, i miss something and i didn't find it.
Thank you.
ButtonStyle Class :
Public Class ButtonStyle
Inherits System.Windows.Controls.Button
Public Sub New()
End Sub
Public Shared ReadOnly BackgroundColorProperty As DependencyProperty =
DependencyProperty.Register("BackgroundColor", GetType(Brush),
GetType(ButtonStyle))
Public Property BackgroundColor As Brush
Get
Return CType(GetValue(BackgroundColorProperty), Brush)
End Get
Set(value As Brush)
SetValue(BackgroundColorProperty, value)
End Set
End Property
Public Shared ReadOnly BackgroundColorHoverProperty As DependencyProperty
= DependencyProperty.Register("BackgroundColorHover", GetType(Brush),
GetType(ButtonStyle))
Public Property BackgroundColorHover As Brush
Get
Return CType(GetValue(BackgroundColorHoverProperty), Brush)
End Get
Set(value As Brush)
SetValue(BackgroundColorHoverProperty, value)
End Set
End Property
Public Shared ReadOnly BorderColorProperty As DependencyProperty =
DependencyProperty.Register("BorderColor", GetType(Brush),
GetType(ButtonStyle))
Public Property BorderColor As Brush
Get
Return CType(GetValue(BorderColorProperty), Brush)
End Get
Set(value As Brush)
SetValue(BorderColorProperty, value)
End Set
End Property
Public Shared ReadOnly BorderColorHoverProperty As DependencyProperty =
DependencyProperty.Register("BorderColorHover", GetType(Brush),
GetType(ButtonStyle))
Public Property BorderColorHover As Brush
Get
Return CType(GetValue(BorderColorHoverProperty), Brush)
End Get
Set(value As Brush)
SetValue(BorderColorHoverProperty, value)
End Set
End Property
Public Shared ReadOnly IconeProperty As DependencyProperty =
DependencyProperty.Register("Icone", GetType(ImageSource),
GetType(ButtonStyle))
Public Property Icone As ImageSource
Get
Return CType(GetValue(IconeProperty), ImageSource)
End Get
Set(value As ImageSource)
SetValue(IconeProperty, value)
End Set
End Property End Class
MainWindow.xaml :
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DictionaryResources.xaml"/>
</ResourceDictionary.MergedDictionaries >
</ResourceDictionary>
</Window.Resources>
<Grid>
<local:ButtonStyle Width="150" Height="50" Style="{StaticResource
StyleBoutonHello}" Icone="img.png" BorderColor="red"
BackgroundColor="red" BackgroundColorHover="Blue" BorderColorHover="blue"
Content="Hello"></local:ButtonStyle>
</Grid>
DictionnaryResource.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<Style x:Name="StyleBoutonHello" x:Key="StyleBoutonHello" TargetType="
{x:Type local:ButtonStyle}">
<Setter Property="BorderBrush" Value="{Binding BorderColor}" />
<Setter Property="Background" Value="red" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Content" Value="Hello" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Image Name="Img" Width="30" Height="30" Source="{Binding
Icone, RelativeSource={RelativeSource TemplatedParent}}" Stretch="None"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" TargetName="Img"
Value="{Binding Icone, RelativeSource={RelativeSource TemplatedParent}}"
/>
<Setter Property="Background" Value="{Binding
BackgroundColorHover}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
For Example:-
1) Implement INotifyPropertyChanged Interface.
Class MainWindow
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Shared ReadOnly MyProperty1 As DependencyProperty = DependencyProperty.Register("UserName", GetType(String), GetType(MainWindow), New UIPropertyMetadata(String.Empty))
Public Property UserName() As String
Get
Return DirectCast(GetValue(MyProperty1), String)
End Get
Set
SetValue(MyProperty1, Value)
OnPropertyChanged("Name")
End Set
End Property
End Class
2) Now bind the value in XAML
<TextBlock Text="{Binding UserName,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
Now whenever you change UserName it will reflect to UI!!!.
I'm pulling data from a CSV file, parsing it in a DataTable, and then, setting this DataTable as the ItemsSource of a DataGrid. I'm then looping the DataTable to do some verifications on the data, and I want to color the DataGrid rows accordingly.
The problem is, I can't find the corresponding DataGrid row based on the DataTable row.
Here's my code :
Dim dg As New DataGrid
Dim dataTable as DataTable = ParseFile(filePath)
Dim statutList() As String = {"Saisi", "Validé", "Suspendu", "Annulé"}
dg.ItemsSource = dataTable.DefaultView
For Each row As DataRow In dataTable.Rows
'This line is what I tried, but it always returns nothing
Dim dgrow As DataGridRow = dg.ItemContainerGenerator.ContainerFromItem(row)
If Not statutList.Contains(row("Statut").ToString) Then
dgrow.Background = Brushes.Red
End If
Next
The problem comes from this line, which doesn't work :
Dim dgrow As DataGridRow = dg.ItemContainerGenerator.ContainerFromItem(row)
SOLUTION :
Both mm8 solutions work. In my case, I used :
dg.UpdateLayout()
For Each row As DataRowView In dg.Items.OfType(Of DataRowView)
Dim dgrow As DataGridRow = dg.ItemContainerGenerator.ContainerFromItem(row)
If Not statutList.Contains(row("Statut").ToString) Then
dgrow.Background = Brushes.Red
End If
Next
The correct "WPF" way of doing this would be define a RowStyle with one or more data triggers that set the background colour of the row when the "Statut" column of that particular row returns any particular value(s), e.g.:
<DataGrid x:Name="dg">
<DataGrid.Resources>
<SolidColorBrush x:Key="color" Color="Red" />
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Statut}" Value="Saisi">
<Setter Property="Background" Value="{StaticResource color}" />
</DataTrigger>
<DataTrigger Binding="{Binding Statut}" Value="Validé">
<Setter Property="Background" Value="{StaticResource color}" />
</DataTrigger>
<DataTrigger Binding="{Binding Statut}" Value="Suspendu">
<Setter Property="Background" Value="{StaticResource color}" />
</DataTrigger>
<DataTrigger Binding="{Binding Statut}" Value="Annulé">
<Setter Property="Background" Value="{StaticResource color}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
Your current code-behind approach would okay work if you modify it slightly:
For Each row As DataRowView In dg.Items.OfType(Of DataRowView)
Dim dgrow As DataGridRow = dg.ItemContainerGenerator.ContainerFromItem(row)
If Not statutList.Contains(row("Statut").ToString) Then
dgrow.Background = Brushes.Red
End If
Next
Note that the ItemContainerGenerator.ContainerFromItem method won't actually return a DataGridRow container for items that may have been virtualized away if the DataTable contains a lot of rows and you haven't disabled UI virtualization:
<DataGrid x:Name="dg" VirtualizingPanel.IsVirtualizing="False">.
Obviously disabling the virtualization may lead to performance issues if your DataTable contains a lot of rows.
Also note that need to execute the code once the containers have actually been created, for example when the Loaded event of the window occurs.
Class MainWindow
Dim statutList() As String = {"Saisi", "Validé", "Suspendu", "Annulé"}
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Dim dg As New DataGrid
Dim dataTable As DataTable = ParseFile(filePath)
dg.ItemsSource = dataTable.DefaultView
End Sub
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
For Each row As DataRowView In dg.Items.OfType(Of DataRowView)
Dim dgrow As DataGridRow = dg.ItemContainerGenerator.ContainerFromItem(row)
If Not statutList.Contains(row("Statut").ToString) Then
dgrow.Background = Brushes.Red
End If
Next
End Sub
...
End Class
I'm using this ObservableCollection-Class within my Project: Link
I want to Bind a RibbonMenuButton to a ObservableDictionary<string,bool>:
<r:RibbonMenuButton ItemsSource="{Binding MyDictionary}">
<r:RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type r:RibbonMenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Path=Key}"/>
<Setter Property="IsChecked" Value="{Binding Path=Value}"/>
</style>
</r:RibbonMenuButton.ItemContainerStyle>
</r:RibbonMenuButton>
But I get exceptions because the Value-Properties of the internal IDictionary-KeyValuePairs are readonly. Any Idea how to solve this?
I thought about something like:
<Setter Property="IsChecked" Value="{Binding Source=MyDictionary[{Binding Path=Key}]}"/>
But this won't work 'cause of {Binding} in {Binding}...
This doesn't work, because your dictionary isn't treated as a dictionary but as an IEnumerable<KeyValuePair<string, bool>>. So each RibbonMenuItem is bound to a KeyValuePair<string, bool> with readonly properties Key and Value.
You can do two one things:
1. Use an ObservableCollection<Tuple<string, bool>> instead of the dictionary and bind IsChecked to Item2.
2. Create a little helper class that contains a IsChecked property and change your dictionary to contain that class as the value and bind IsChecked to Value.IsChecked.
I would go with answer two, because the needed changes and possible side effects are smaller.
My answer assumes that you want to have a two way binding on IsChecked. If not, go with the answer of slugster.
WPF binding is two-way by default. Make it one-way and see if that solves your issue.
<r:RibbonMenuButton ItemsSource="{Binding MyDictionary}">
<r:RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type r:RibbonMenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Key, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Value, Mode=OneWay}"/>
</style>
</r:RibbonMenuButton.ItemContainerStyle>
</r:RibbonMenuButton>
Here is a reference for you: MSDN Windows Presentation Foundation Data Binding: Part 1 (specifically check the section Binding Mode close to the bottom of the page)
If You want to bind MenuItems to Dictionary<string, bool> without using a helper class, like the accepted answer suggests, here is the minimal-change solution (no need to add anything else):
define a Click event inside the ItemContainerStyle whose ClickEventHandler will update the dicitonary.
declare a dictionary and initialize it inside the UserControl's / Window's constructor
In code:
MainWindow.xaml:
<MenuItem Header="_My settings" ItemsSource="{Binding MySettings}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Key, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Value, Mode=OneWay}"/>
<!-- this is the main line of code -->
<EventSetter Event="Click" Handler="MySettings_ItemClick"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
// properties...
// Declaration of the dictionary
public Dictionary<string, bool> MySettings{ get; set; }
public MainWindow()
{
InitializeComponent();
// Initialize the dictionary
MySettings = new Dictionary<string, bool>()
{
{ "SettingOne", true}
// Other pairs..
};
}
// other things..
// ClickEvent hanlder
private void MySettings_ItemClick(object sender, RoutedEventArgs e)
{
MenuItem clickedItem = (sender as MenuItem);
MySettings[clickedItem.Header as string] = clickedItem.IsChecked;
}
} // end of MainWindow class
That's it! You're all set!
Credits to slugster and his answer for XAML code for OneWay binding :)
As a general solution to this problem of binding to dictionaries I created an UpdateableKeyValuePair and return that instaed of the usual KeyValuePair. Here is my class:
public class UpdateableKeyValuePair<TKey,TValue>
{
private IDictionary<TKey, TValue> _owner;
private TKey _key;
public UpdateableKeyValuePair(IDictionary<TKey, TValue> Owner, TKey Key_)
{
_owner = Owner;
_key = Key_;
}
public TKey Key
{
get
{
return _key;
}
}
public TValue Value
{
get
{
return _owner[_key];
}
set
{
_owner[_key] = value;
}
}
}
I have the following xaml inside a text box element that is part of a combo box item template. The combobox's items source is set to a list of objects that have a boolean property AcceptsInput everything works great but I can't get this trigger to fire do I have to do something else.
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding AcceptsInput}" Value="False" >
<Setter Property="Visibility" Value="Hidden"> </Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
Are you correctly implementing INotifyPropertyChanged in the viewmodel class with the AcceptsInput property?
It should look something like this:
public class MyClass: INotifyPropertyChanged
{
private bool _acceptsInput;
public bool AcceptsInput
{
get { return _acceptsInput; }
set
{
_acceptsInput = value;
OnPropertyChanged("AcceptsInput");
}
}
...
}
I have a ItemsControl with a collection of objects. I wan't to be able to click the object and then get a panels with more info.
So I decided to style the DataTemplate for the items in the ItemsControl as a button and that seems to work fine. However I have no idea how to set the click event of this button in the style. It says I should use a EventSetter but I can't get that to work.
Here is the code:
<Style TargetType="Expander" >
<Style.Resources>
<Style TargetType="ItemsControl" >
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border BorderThickness="0,1,0,1" BorderBrush="{StaticResource DarkColorBrush}" >
<ScrollViewer Margin="0" VerticalScrollBarVisibility="Auto"
Focusable="false">
<StackPanel Margin="2" IsItemsHost="True" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type data:CompanyViewModel}" >
<Button>
<Button.Resources>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="Bd" BorderBrush="{StaticResource DarkColorBrush}"
BorderThickness="1"
Margin="5"
CornerRadius="8">
<Border.Background>
<!-- Removed for brevity -->
</Border.Background>
<StackPanel Orientation="Vertical">
<TextBlock Margin="5" Text="{Binding Path=Name}" Style="{StaticResource MenuText}" FontSize="16" HorizontalAlignment="Center" />
<TextBlock Margin="5,0,5,5" Text="{Binding Path=Code, StringFormat=Kt. {0}}" Style="{StaticResource MenuText}" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Bd" Property="Background">
<Setter.Value>
<!-- Removed for brevity -->
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed" Value="true">
<Setter TargetName="Bd" Property="Background">
<Setter.Value>
<!-- Removed for brevity -->
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Resources>
</Button>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="Expander">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="1"
IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter Name="Content" Grid.Column="0" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="Content" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Decided to add what I wanted to accomplish with the button click:
<Button Click="CompanyClick" />
CompanyClick being defined in the code behind.
Change
<Button>
To...
<Button Command="{Binding OnClick}" />
On the class you use as an item in this ItemsControl, implement a read-only property which returns an ICommand for the button to use.
EDIT:
For this example, I made use of an implementation of ICommand called RelayCommand, which is available at http://msdn.microsoft.com/en-us/magazine/dd419663.aspx. See figure 3 of that article for the full RelayCommand class in C#. I converted it to Visual Basic for my use, that code is below. It does nothing more than automate the registration of commands with the WPF system, and provides you with a convenient constructor:
''' <summary>
''' Implements the ICommand interface
''' </summary>
''' <remarks>
''' Thanks to Josh Smith for this code: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
''' </remarks>
Public Class RelayCommand
Implements ICommand
#Region "Fields"
Private ReadOnly _execute As Action(Of Object)
Private ReadOnly _canExecute As Predicate(Of Object)
#End Region ' Fields
#Region "Constructors"
Public Sub New(ByVal execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_execute = execute
_canExecute = canExecute
End Sub
#End Region ' Constructors
#Region "ICommand Members"
<DebuggerStepThrough()>
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return If(_canExecute Is Nothing, True, _canExecute(parameter))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_execute(parameter)
End Sub
#End Region ' ICommand Members
End Class
Using that class, you can then implement ICommands on your ViewModel, by exposing an ICommand as a read-only property in that class, along with a backing field to store the RelayCommand, which, don't forget, implements ICommand. Here's a truncated sample:
Public Class CompanyViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _OnClick As RelayCommand
Public ReadOnly Property OnClick As ICommand
Get
If _OnClick Is Nothing Then
_OnClick = New RelayCommand(Sub()
Me.OnClickExecute()
End Sub,
Function()
Return Me.OnClickCanExecute()
End Function)
End If
Return _OnClick
End Get
End Property
Private Function OnClickCanExecute() As Boolean
' put a test here to tell the system whether conditions are right to execute your command.
' OR, just return True and it will always execute the command.
End Function
Private Sub OnClickExecute()
' put the processing for your command here; THIS IS YOUR EVENT HANDLER
End Sub
' .... implement the rest of your ViewModel
End Class
The "OnClick" name is not required; the commands can take any name, because the system is not convention-based the way that VB6 was with its event handlers.
There is more than one way to do this. I'm intrigued by the "Caliburn.Micro" implementation of ICommand, which is convention-based and might make things more readable, depending on your style. But, Caliburn is an open-sourced effort by an enthusiast, albeit a very competent and qualified enthusiast. Google or Bing "Caliburn.Micro" for more information on that.
There's also this:
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<!-- Animations manipulating the button here -->
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!-- The rest of your triggers here -->
</ControlTemplate.Triggers>
This kind of mechanism in your template will give you control over the properties of the button, and possibly properties in other parts of the visual tree, depending on where you put the definiton.
You might also consider architecting things a bit differently. I wouldn't necessarily pile all the definitions into the style in quite the same way.