I am making a custom control based on a button, and I want to bind the width of the button to a property of the class. I have looked at this, this, and this, but they either aren't what I'm looking for, or don't work.
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControl">
<Style TargetType="{x:Type local:MyCustomControl}" BasedOn = "{StaticResource {x:Type Button}}">
<Setter Property = "Background" Value = "LightSalmon" />
<Setter Property = "Foreground" Value = "Blue"/>
<Setter Property = "Height" Value = "50"/>
<Setter Property = "Width" Value = "{Binding MyCustomControl.TextBinding}"/>
<Setter Property = "VerticalAlignment" Value = "Top"/>
<Setter Property = "Margin" Value="10"/>
</Style>
</ResourceDictionary>
MyCustomControl.cs
namespace CustomControl
{
public class MyCustomControl : Button
{
double m_textBinding = 50;
public double TextBinding
{
get { return m_textBinding; }
set { m_textBinding = value; }
}
static MyCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl),
new FrameworkPropertyMetadata(typeof(MyCustomControl)));
}
}
}
If need be, I can just use the "setter" function, and specify manually, "Width = value;", but I would prefer to use a binding. Currently the "{Binding MyCustomControl.TextBinding}" isn't working.
This should work:
<Setter Property="Width"
Value="{Binding TextBinding, RelativeSource={RelativeSource Self}}"/>
Related
I have a user control for which I have to change color, based on mouse hover, click or none. Following MVVM. This is the code I have:
User control in XAML
<userControls:NC DataContext="{Binding NCVM}" >
</userControls:NC>
User Control View Model
public class NCVM : ObservableObject
{
public NCVM()
{
}
private NCState _currentState = NCState.InActive;
public NCState CurrentState
{
get => _currentState;
set
{
_currentState = value;
switch (_currentState)
{
case NCState.InActive:
ForegroundColor = System.Windows.Media.Brushes.LightGray;
IsActive = false;
break;
case NCState.Active:
ForegroundColor = System.Windows.Media.Brushes.White;
IsActive = true;
break;
case NCState.Hovered:
ForegroundColor = System.Windows.Media.Brushes.White;
IsActive = false;
break;
default:
ForegroundColor = System.Windows.Media.Brushes.LightGray;
IsActive = false;
break;
}
}
}
public bool _isActive;
public bool IsActive
{
get => _isActive;
set => SetProperty(ref _isActive, value);
}
private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.LightGray;
public System.Windows.Media.Brush ForegroundColor
{
get => _foregroundColor;
set => SetProperty(ref _foregroundColor, value);
}
}
Main Window View Model
public class MWVM : BVM
{
#region Private Variables
private NCVM _NCVM = new();
#endregion
public MWVM()
{
NCVM.CurrentState = NCState.Active;
}
#region Public Properties
public NCVM NCVM
{
get => _NCVM;
set => SetProperty(ref _NCVM, value);
}
#endregion
}
Right now, it's getting preset as active for checking. Now, I have to make it manual so it changes on hover, but not getting how to do with binding.
The MVVM pattern is about separating the user interface (view) from the data and application logic itself. Your example violates MVVM in that it stores the brushes and the visual states in a view model. The view model should only expose data and commands to be bound, but not user interface elements and it must not contain logic to that relates to the user interface just like managing visual states or appearance. It is too often misunderstood as creating a view model and just putting everything there.
In your case, I think that you can solve your issue by moving everything into a style. The following XAML should show your userControls:NC. There are triggers for different states like Disabled, Hover / Mouse Over. Please note that you need to set a Background, otherwise the control does not participate in hit testing and e.g. the IsMouseOver property will not be True even if you hover over it. For no background use Transparent (which is not equal to not setting a value).
<UserControl ...>
<UserControl.Style>
<Style TargetType="{x:Type userControls:NC}">
<!-- Background must be set at least to "Transparent" -->
<Setter Property="Background" Value="Black"/>
<!-- Default -->
<Setter Property="Foreground" Value="LightGray"/>
<Style.Triggers>
<!-- Hovered -->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="White"/>
</Trigger>
<!-- Disabled -->
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<!-- Dummy element for demonstration purposes of foreground -->
<TextBlock Text="This text shows the foreground"/>
</UserControl>
You may take a look at EventTrigger, or Triggers in general to style your control.
*Edit:
A little example, MVVM not considered, just for you to get a glimpse at triggers.
UserControl:
<UserControl x:Class="WpfApp1.UserControl1"
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:DataContext="{d:DesignInstance Type={x:Type local:UserControl1}}"
Height="200" Width="400">
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMyPropSet}" Value="True">
<Setter Property="Background" Value="Turquoise"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<GroupBox Header="I am your usercontrol">
<Button Width="100" Height="35" Content="Toggle Property" Click="Button_Click"/>
</GroupBox>
</UserControl>
and code-behind:
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsMyPropSet { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
IsMyPropSet = !IsMyPropSet;
RaisePropertyChanged(nameof(IsMyPropSet));
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Trying to setup the background of a cell dependend on a cell-object property in a WPF DataGrid I get an error, that the property is not found (but on the row-object):
System.Windows.Data Error: 40 : BindingExpression path error: 'IsOn' property not found on 'object' ''MyRow' (HashCode=48826322)'. BindingExpression:Path=IsOn; DataItem='MyRow' (HashCode=48826322); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')
I wonder, why the DataTrigger Binding is addressing the row object "MyRow", since the DataTrigger is defined for/inside a CellStyle.
XAML:
<DataGrid Name="tblTest" Grid.Column="2" IsReadOnly="True" AutoGenerateColumns="True">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="PaleGreen" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsOn}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
C#
class MyCell
{
public MyCell( string v)
{
Value = v;
}
public string Value { get; set; }
public bool IsOn { get => Value == "one"; }
public override string ToString()
{
return Value;
}
}
class MyRow
{
public MyCell One { get; set; }
public MyCell Two { get; set; }
}
void SetupTestTable()
{
List<MyRow> data = new();
data.Add(new MyRow
{
One = new MyCell("one"),
Two = new MyCell("two")
});
tblTest.ItemsSource = data;
}
So how to bind against the cell object "MyCell" correctly?
DataGridCells have the same DataContext as DataGridRow - there are many obstacles to do differently in general-purpose manner. So single DataGrid.CellStyle won't work
I will use AutoGeneratingColumn to create cell styles for each column. However they will be based on existing style which is stored in DataGrid.Resources.
<DataGrid Name="tblTest" Grid.Column="2" IsReadOnly="True"
AutoGenerateColumns="True"
AutoGeneratingColumn="tblTest_AutoGeneratingColumn">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}" x:Key="ColoredCellStyle">
<Setter Property="Background" Value="Cyan" />
<Style.Triggers>
<DataTrigger Binding="{Binding Tag.IsOn, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
I'm using binding to Tag instead of DataContext, because DataContext is MyRow object. In Tag there will be MyCell objects. It is achieved in event handler:
private void tblTest_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.Column is DataGridTextColumn tc && tc.Binding is Binding binding)
{
// unique value for each column
var property = binding.Path.Path;
// DataGrid reference to get Resources
var dg = (DataGrid)sender;
// new cell style which inherits trigger from ColoredCellStyle and binds Tag to MyCell property
var cellStyle = new Style
{
TargetType = typeof(DataGridCell),
BasedOn = (Style)dg.Resources["ColoredCellStyle"],
Setters =
{
new Setter
{
Property = DataGridCell.TagProperty,
Value = new Binding(property)
}
}
};
tc.CellStyle = cellStyle;
};
}
I have a class that inherits from TextBox
public class DecimalTextBox : TextBox
{
#region Float Color
public static readonly DependencyProperty FloatColorProperty = DependencyProperty.Register("FloatColor", typeof(Color), typeof(DecimalTextBox), new FrameworkPropertyMetadata(Colors.Red));
public Color FloatColor
{
get { return (Color)GetValue(FloatColorProperty); }
set { SetValue(FloatColorProperty, value); }
}
#endregion
. . . . .. OTHER STUFF
}
I want to style this control using something like this:
<Style x:Key="DecimalTextBoxGridStyle" TargetType="DecimalTextBox">
<Setter Property="TextAlignment" Value="Right"/>
<Setter Property="FloatColor" Value="Black"/>
<Setter Property="BorderBrush" Value="Transparent"/>
</Style>
But it style told me
DecimalTexbox type isn't admited in wpf project
How can I do to that ?
There is another approach ?
Include the XAML namespace:
<Style x:Key="DecimalTextBoxGridStyle" TargetType="local:DecimalTextBox">
where local is mapped to the CLR namespace in which the DecimalTextBox class is defined:
<Window ...
xmlns:local="clr-namespace:WpfApplication1"
[My main idea of this is to set visible/hidden for a usercontrol. I used WPF with Mvvmcross.]
I have a user control call SpinningWheelUserControl. I want to visible/hide it with the datatrigger. Below is my xaml code in App.xaml
In App.xaml I have added the namespace of the usercontrol as below.
xmlns:local="clr-namespace:UserControl"
The following is a style setting for my usercontrol.
<Style x:Key="SpinningWheel" TargetType="{x:Type local:SpinningWheelUserControl}" >
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
There is a class for SpinningWheel
public class SpinningWheelViewModel
: MvxNotifyPropertyChanged
{
public bool IsVisible { get; set; }
}
In a constructor of parent class, i use like this code
SpinningWheel = new SpinningWheelViewModel();
SpinningWheel.IsVisible = false;
The usercontrol is hidden for a first running. But when I change the IsVisble to true, it has no change.
SpinningWheel.IsVisible = true
You need to set Visibility instead of IsVisible like this:
SpinningWheel.Visibility = Visibility.Visible;
Oh now i see, you are setting your custom IsVisibility instead of UIElement property.
Issue with your code is you haven't raised PropertyChanged to let UI know that some property change in underlying source object.
private bool isVisible;
public bool IsVisible
{
get { return isVisible;}
set
{
if(isVisible != value)
{
isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
Assuming you have implemented INotifyPropertyChanged on your class.
This n+1 video called N=34 : a data-bound busy dialog shows exactly how to do what you are trying to do.
I have a User Interface and I am changing the background property of the main grid. Now some give a very pleasent look but for some there is difficulty reading the text as displayed. However, a problem arises when I have approximately 20 labels around there now, and making them change and assign them color each time is making my code look ugly. I know there must be a more elegant design.
I tried to bind labels to a color but that does not work. here is code
XAML:
<Label Foreground="{Binding defColor}" Content="Settings" Height="44" HorizontalAlignment="Left" Margin="12,53,0,0" Name="label1" VerticalAlignment="Top" FontWeight="Normal" FontSize="26" />
Code behind:
SolidColorBrush defColor = new SolidColorBrush();
public SettingsWindow()
{
InitializeComponent();
defColor.Color = Colors.Black;
//defColor.Color = Colors.Black; label1.Foreground = defColor;
}
private void button4_Click(object sender, RoutedEventArgs e)
{
defColor.Color = Colors.Black;
}
Thanks
Just from looking at the C# code you posted, I think your first problem is that you've done this
SolidColorBrush defColor = new SolidColorBrush();
instead of this
public SolidColoRBrush defColor { get; set; }
You can only bind to properties.
Your constructor will now look like this
public SettingsWindow()
{
InitializeComponent();
defColor = new SolidColorBrush(Colors.Black);
this.DataContext = this; // need to tell your window where to look for binding targets
}
If you are setting the SettingsWindow DataContext = this; then the SettingsWindow class MUST implement INotifyPropertyChanged. to get the {Binding defColor} to work. Code you need:
public class SettingsWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SettingsWindow()
{
// We are acting as our own 'ViewModel'
DataContext = this;
InitializeComponent();
}
private Color _defColor;
public Color defColor
{
get { return _defColor; }
set
{
if (_defColor != value)
{
_defColor = value;
if(null != PropertyChanged)
{
PropertyChanged(this, "defColor");
}
}
}
}
}
As for targeting all labels in your application, the correct approach is to use Style, as previously suggested.
You will have to apply this style to each Label. Omit the x:Key to make it default style for your Labels
<Style x:Key="LabelForeGroundStyle" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="{Binding defColor}" />
</Style>
Instead of Binding each label to the same property, I think you should use style, and apply this style for each label, e.g. with your binding:
<Style x:Key="LabelForeGroundStyle" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="{Binding defColor}" />
</Style>
or even better, with trigger:
<Style.Triggers>
<Trigger>
<Trigger Property="Background" Value="Blue">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Trigger>
</Style.Triggers>