In WPF it was a bit more confusing how to bind colors, like background color to a viewmodel property.
Are there other ways to bind Colors in Avalonia ?
Or is this example a good way ?
Avalonia View
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Button.Views.MainWindow"
Title="Button" Width="700">
<StackPanel Grid.Column="2" Orientation="Vertical" Gap="8" Margin="10">
<TextBox Name="Textbox3" Text="{Binding Textbox3Text}" Foreground="{Binding Textbox3Foreground}"/>
</StackPanel>
</Window>
Avalonia ViewModel
public class MainWindowViewModel
{
private IBrush _textbox3Foreground;
public IBrush Textbox3Foreground
{
get { return _textbox3Foreground; }
set
{
this.RaiseAndSetIfChanged(ref _textbox3Foreground, value);
}
}
public MainWindowViewModel()
{
Textbox3Foreground = Brushes.DarkOliveGreen;
}
}
Make sure that you have set the DataContext of the window to an instance of your view model class:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Button.Views.MainWindow"
Title="Button" Width="700">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel Grid.Column="2" Orientation="Vertical" Gap="8" Margin="10">
<TextBox Name="Textbox3" Text="{Binding Textbox3Text}" Foreground="{Binding Textbox3Foreground}"/>
</StackPanel>
</Window>
In general you don't usually define UI related things such as colours in the view model though. These kind of things are usually defined directly in the view without any bindings. But you can certainly bind to a Brush property like this.
Related
new to .NET ecosystem.
The following code is working fine following MVVM in a blank UWP app.
<Page
x:Class="UWP.Blank.Mvvm.App01.Views.PrincipalPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWP.Blank.Mvvm.App01"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:md="using:UWP.Blank.Mvvm.App01.Models"
xmlns:vm="using:UWP.Blank.Mvvm.App01.ViewModels"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
x:Name="ThisPrincipalPage">
<Page.Resources>
<DataTemplate x:Name="FlipView_ItemTemplate" x:DataType="md:MyFlipViewItem">
<Grid>
<Image Source="{x:Bind ImageLocation}" Stretch="Uniform" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="AdaptativeGridView_ItemTemplate">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<FlipView Width="300" Height="300"
ItemTemplate="{StaticResource FlipView_ItemTemplate}"
ItemsSource="{Binding ElementName=ThisPrincipalPage, Path=ViewModel.FlipViewData}"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<controls:AdaptiveGridView x:Name="AdapGridView1"
ItemsSource="{x:Bind Path=ViewModel.AdaptativeGridViewData}"
ItemTemplate="{StaticResource AdaptativeGridView_ItemTemplate}"/>
</Grid>
</Page>
Which looks like:
enter image description here
The code behind is:
public sealed partial class PrincipalPage : Page
{
// Reference to our view model.
public PrincipalViewModel ViewModel { get; set; }
public PrincipalPage()
{
this.InitializeComponent();
this.ViewModel = new PrincipalViewModel();
}
}
And the ViewModel is an instance of this class:
public class PrincipalViewModel
{
public ObservableCollection<int> AdaptativeGridViewData { get; set; } = new ObservableCollection<int>() { 1, 2, 3, 4 };
public ObservableCollection<MyFlipViewItem> FlipViewData { get; set; } = new ObservableCollection<MyFlipViewItem>();
...
}
that provides two lists, one for the AdaptativeGridView control, and another for all the FlipViews inside of it.
My question is, is there any way I can use x:Bind only? Basically, because it claims to be faster. In case it is not possible, x:Bind is not a substitute of Binding, and makes everything even more confusing.
I tried two basic "intuitive options" which don't work.
Option 1, where ItemsSource property of the FlipView is changed to use x:Bind. Error claims:
Invalid binding path 'ViewModel.FlipViewData' : Property 'ViewModel' not found on type 'DataTemplate'.
Seems FlipView's DataTemplate has a DataContext different than expected. Maybe the DataContext is pointing to ViewModel.AdaptativeGridViewData, but, who knows? It is XAML. This is one of the reasons I am finding XAML an inconvenient since it behaves like a "black box". You know how to do something or you are in trouble.
In this case I tried to use classic Binding with "FindAncestor" utility to point back to ViewModel. In UWP however, AncestorType is not implemented.
(i.e., alike --> ItemsSource="{Binding FlipViewData, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type AdaptativeGridView}}}" )
<DataTemplate x:Key="AdaptativeGridView_ItemTemplate">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<FlipView Width="300" Height="300"
ItemTemplate="{StaticResource FlipView_ItemTemplate}"
ItemsSource="{x:Bind Path=ViewModel.FlipViewData}"/>
</Grid>
</DataTemplate>
I am curious since I am not explicitely setting the x:DataType in the DataTemplate. So, why the DataContext is not the same as the AdaptativeGridView or any "normal" control in the Page?
Option 2, which explicitely sets x:DataType of the DataTemplate (note this is indeed working in the other DataTemplate "FlipView_ItemTemplate"). However, for the "AdaptativeGridView_ItemTemplate", there is a runtime error.
Exception thrown: 'System.InvalidCastException' in UWP.Blank.Mvvm.App01.exe
and no more clues.
<DataTemplate x:Key="AdaptativeGridView_ItemTemplate" x:DataType="vm:PrincipalViewModel">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<FlipView Width="300" Height="300"
ItemTemplate="{StaticResource FlipView_ItemTemplate}"
ItemsSource="{x:Bind FlipViewData}"/>
</Grid>
</DataTemplate>
Any experience or suggestions are welcome.
<UserControl x:Class="WatermarkTextBox"
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"
mc:Ignorable="d"
d:DesignHeight="30"
d:DesignWidth="250">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<Border>
<Grid x:Name="grid">
<TextBlock Text="{Binding Watermark, FallbackValue=This prompt dissappears as you type...}"
Visibility="{Binding ElementName=txtUserEntry, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBox Name="txtUserEntry"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Border>
</UserControl>
The above code shows my WatermarkTextBox control. In the code behind file I have set the DataContext. I left out all the code for the DP's of the control.
public WatermarkTextBox()
{
InitializeComponent();
grid.DataContext = this;
}
I had to bind the DataContext to the grid because otherwise the Text properties of both the watermark and actual text wouldn't display. The problem now is that I can't set the Background of the Border outside of the Grid.
I tried the code below but then only the Background of the Border is set and not the watermark and actual text.
public WatermarkTextBox()
{
InitializeComponent();
this.DataContext = this;
grid.DataContext = this;
}
In a UserControl like this you should never exlicitly set the DataContext to this or anyting else, because the DataContext is usually set externally when you use the UserControl somewhere in your application. The externally applied DataContext is typically (part of) the application's view model.
You should instead change your internal bindings so that they use an explicit RelativeSource:
<TextBlock
Text="{Binding Path=Watermark,
RelativeSource={RelativeSource AncestorType=UserControl},
FallbackValue=This prompt dissappears as you type...}"
Visibility="{Binding ElementName=txtUserEntry,
Path=Text.IsEmpty,
Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBox
Name="txtUserEntry"
Text="{Binding Path=Text,
UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
and then remove any DataContext assignment from the UserControl's constructor.
See e.g. this answer (and many other similar) that discuss this topic in detail.
Hallo and thank you for your time.
I have a peculiar problem. I have created a usercontol, which has some custom dependency properties.
If i implement the usercontrol, and bind to a static text. Everything is working fine.
However, if i try to set it to the value of a selected objects properties. It does not work.
This is the error i am getting in the output window:
Error: BindingExpression path error: 'SelectedUseCase' property not
found on 'Helper.UserControls.UseCasePropertyDisplay'.
BindingExpression: Path='SelectedUseCase.Name'
DataItem='Helper.UserControls.UseCasePropertyDisplay'; target element
is 'Helper.UserControls.UseCasePropertyDisplay' (Name='null'); target
property is 'Text' (type 'String')
UserControl:
https://github.com/Toudahl/SoftwareDesignHelper/blob/master/Helper/UserControls/DisplayAndEditControl.xaml
<UserControl
x:Class="Helper.UserControls.UseCasePropertyDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Helper.UserControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Horizontal"
Margin="260,0,0,0">
<TextBlock Text="{Binding Label}"
Style="{StaticResource UseCaseTextBlock}"
x:Name="textblock_label"/>
<TextBlock x:Name="textblock_propertyContent"
Text="{Binding Text}"
Style="{StaticResource UseCaseFrameWorkTextElement}"
DoubleTapped="textblock_DoubleTapped" />
<TextBox x:Name="textbox_propertyContent"
Text="{Binding Text}"
Visibility="Collapsed"
Style="{StaticResource UseCaseTextBox}"
LostFocus="textbox_LostFocus" />
</StackPanel>
</UserControl>
Decleration of dependency properties in codebehind:
https://github.com/Toudahl/SoftwareDesignHelper/blob/master/Helper/UserControls/DisplayAndEditControl.xaml.cs
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(UseCasePropertyDisplay),
new PropertyMetadata(null));
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
"Label",
typeof(string),
typeof(UseCasePropertyDisplay),
new PropertyMetadata(null));
public string Text
{
get { return (string)GetValue(TextProperty); }
set {SetValue(TextProperty, value);}
}
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value);}
}
This is how i implement it on in the view:
https://github.com/Toudahl/SoftwareDesignHelper/blob/master/Helper/ViewsAndViewModels/ViewUseCases.xaml
<uc:UseCasePropertyDisplay Label="Name" Text="{Binding SelectedUseCase.Name, Mode=TwoWay}" />
From reading very similar questions here, im guessing that it has something to do with the way i set the context. However, the solution that has been provided to people (setting the relative source to the ancestor), doesnt work for me. Since its not available on my platform.
I am not really sure where to go from here, as this is the first time i try to use usercontrols, and the first time i use dependency properties.
School doesnt start untill a few weeks, so i cant get a hold of my teacher for this.
Instead of setting the DataContext global, set the first ui element to the inherited DataContext:
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
As you have written, you can not use FindAncestor so you can try using a name and reference to it:
<UserControl
x:Class="Helper.UserControls.UseCasePropertyDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Helper.UserControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
x:Name="Root">
<StackPanel DataContext="{Binding ElementName=Root}"
Orientation="Horizontal"
Margin="260,0,0,0">
<TextBlock Text="{Binding Label}"
Style="{StaticResource UseCaseTextBlock}"
x:Name="textblock_label"/>
<TextBlock x:Name="textblock_propertyContent"
Text="{Binding Text}"
Style="{StaticResource UseCaseFrameWorkTextElement}"
DoubleTapped="textblock_DoubleTapped" />
<TextBox x:Name="textbox_propertyContent"
Text="{Binding Text}"
Visibility="Collapsed"
Style="{StaticResource UseCaseTextBox}"
LostFocus="textbox_LostFocus" />
</StackPanel>
</UserControl>
I'm trying to send a command from one UserControl to another. The first one contains a button and the second one contains a custom class that derives from the Border class.
I want when I click the Button in UserControl to execute the Redraw method in CustomBorder in UserControl2.
Here is what I have done so far.
MainWindow.xaml:
<Window x:Class="SendCommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sendCommands="clr-namespace:SendCommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<sendCommands:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<sendCommands:UserControl1 Grid.Row="0"/>
<sendCommands:UserControl2 Grid.Row="1"/>
</Grid>
</Window>
UserControl1:
<UserControl x:Class="SendCommands.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Redraw"
Width="200"
Height="30"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</UserControl>
UserControl2:
<UserControl x:Class="SendCommands.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sendCommands="clr-namespace:SendCommands">
<Grid>
<sendCommands:CustomBorder Background="Black">
</sendCommands:CustomBorder>
</Grid>
</UserControl>
CustomBorder class:
using System.Windows;
using System.Windows.Controls;
namespace SendCommands
{
public class CustomBorder : Border
{
public void Redraw()
{
// Operations to redraw some elements inside the CustomBorder
MessageBox.Show("We did it!");
}
}
}
ViewModel.cs:
namespace SendCommands
{
class ViewModel
{
}
}
Please somebody help me learn this once and for all. I'm new to MVVM concept and I have read a lot but no results. I really need a practical solution to get the concepts right.
What does your Redraw method is really supposed to do? Change border if some property has changed? E.g. an item in a shop was sold out?
In general view should reflect changes in the ViewModel. This happens automatically with bindings. View element, such as button, can communicate with ViewModel with Commands.
Therefore your button would look like this:
<Button Command={Binding ClickCommand} />
In your ViewModel you'll have a
public DelegateCommand ClickCommand {get; private set;}
and
ClickCommand = new DelegateCommand(ExecuteClick);
ExecuteClick would update some properties in the view model, e.g. if you have an online shop, set a SoldOut property of bike object to true.
Your view will in turn bind to properties of Bike and change its appearance if some properties change. Changes like text will happen by themselves, more complicated changes can be achieved with converters (e.g. change bckaground to red in SoldOut is true):
<Resources>
<SoldOutToBckgrConverter x:Key="soldOutToBckgrConverter" />
</Resources>
<Label Content={Binding Path=SelectedItem.Model} Background={Binding Path=SelectedItem.SoldOut, Converter={StaticResource soldOutToBckgrConverter}} />
SoldOutToBckgrConverter implements IValueConverter and converts True to Red.
Note: SelectedItem is again bound to a list, whose source is bound to sth like ObservableCollection on your ViewModel.
So basically you shouldn't call redraw, it should all redraw itself automatically with commands, changes in VM and bindings.
Update to your comment: that's what I tried to show, given that I understood the purpose of your redraw right. In my example with products and red background for sold items this will look like this:
In your VM:
public ObservableCollection<MyProduct> Products {get;set;}
private MyProduct selectedProduct;
public MyProduct SelectedProduct
{
get {return selectedProduct;}
set {
if (selectedProduct != value) {
selectedProducat = value;
RaisePropertyChanged(()=>SelectedProduct;
}
}
}
MyProduct has Model property (real world product model, i.e. brand) and SoldOut.
In your View:
<ListBox SelectedItem="{Binding SelectedProduct, Mode=TwoWay}" ItemsSource="{Binding Products}" >
<ListBox.ItemTemplate>
<Label Content={Binding Path=SelectedItem.Model} Background={Binding Path=SelectedItem.SoldOut, Converter={StaticResource soldOutToBckgrConverter}} />
</ListBox.ItemTemplate>
</ListBox>
Now when you click you button, VM changes SelectedProduct and Binding cahnges background (or border..)
You can use "CallMethodAction" behavior provided by Expression Blend. Add System.Windows.Interactivity.dll to your project and you can bind the method to event. In your case "ReDraw" method must bound to "Click" event. More information on this behavior.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<sendCommands:UserControl1 Grid.Row="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RefreshButtonClick">
<ei:CallMethodAction MethodName="RedrawCustomBorder"
TargetObject="{Binding ElementName=customBorder}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</sendCommands:UserControl1>
<sendCommands:UserControl2 Grid.Row="1" x:Name="customBorder"/>
</Grid>
I use Caliburn Micro for my WPF application. I implemented a little UserControl:
<UserControl Name="ImageButtonUserControl"
x:Class="SportyMate.Utility.Controls.ImageButton"
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">
<Grid>
<Button>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ElementName=ImageButtonUserControl, Path=Image}" />
<TextBlock Text="{Binding ElementName=ImageButtonUserControl, Path=Text}" />
</StackPanel>
</Button>
</Grid>
</UserControl>
Now I want to use these Control in my view:
<uc:ImageButton Name="Cancel" Image="/Images/Icons/cancel_16x16.png" Text="Abbrechen" Margin="3" />
When I want to open my view (in my case it's opened as a dialog) it doesn't work. The View does not openend.
When I remove the Name-Attribute everthing is fine, but the Button have no binding to an action. Can anyone tell me what I have to do for a correct binding? A regular Button worked.
You are on a completely wrong way. You shouldn't create a user control for such a simple modification. You need to create a DataTemplate and use it for a Button.ContentTemplate. First you need to define a helper type for button content:
public class ImageButtonContent
{
public BitmapImage Image { get; set; }
public string Label { get; set; }
}
After that you can use it for DataTemplate:
<Window x:Class="Trys.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Trys="clr-namespace:Trys"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<Trys:ImageButtonContent x:Key="YourImageButtonHere"
Label="Your ImageButtonHere">
<Trys:ImageButtonContent.Image>
<BitmapImage UriSource="your-icon.png" />
</Trys:ImageButtonContent.Image>
</Trys:ImageButtonContent>
<DataTemplate x:Key="ImageButton"
DataType="{x:Type Trys:ImageButtonContent}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}"
Margin="5" />
<TextBlock Text="{Binding Label}"
VerticalAlignment="Center"
Margin="10,0,0,0" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Button ContentTemplate="{StaticResource ImageButton}"
Content="{StaticResource YourImageButtonHere}"
Height="50"
Width="250" />
</Grid>
</Window>
I used a resource for an object, but you can use a property on your ViewModel. The result is:
And this just a normal button. You can use for it all power of Caliburn.Micro conventions like Click event default binding. Enjoy!