AvaloniaUI - VIewModel can't be bound to Usercontrol - c#

I'm currently working on an application with AvaloniaUI and C#.net. My application has a MainWindow that uses one ViewModel(called MainWindowViewModel) and the Window also contains two UserControls that are integrated via TabControl.
So the issue that I now have is, that I want give each UserControl its own ViewModel, so far I have created a ViewModel for one of my UserControls and also set the namespace to it in the axaml-File of my Control. I've set the DataContext also, but the ViewModel is never been loaded.
As follows here's the source code of my MainWindow, the UserControl und the ViewModel of my UserControl:
MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views ="clr-namespace:MyApp.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Views.MainWindow"
WindowStartupLocation="CenterScreen"
Icon="/Assets/Programmicon.png"
Title="{Binding WindowTitle}" CanResize="False" >
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Window.Styles>
<Style Selector="TabItem">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Height" Value="34"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Background" Value="#148198"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0 0 0 0"/>
<Setter Property="Padding" Value="10 0"/>
</Style>
<Style Selector="TabControl WrapPanel">
<Setter Property="Background" Value="#148198"/>
</Style>
<Style Selector="TabItem:selected">
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Margin" Value="0 0 0 0"/>
<Setter Property="Padding" Value="10 0"/>
</Style>
</Window.Styles>
<Grid>
<TabControl Name="tabMenu" Background="White">
<TabItem Header="Import" VerticalContentAlignment="Center" >
<views:ImportView/>
</TabItem>
<TabItem Header="Einstellungen" VerticalContentAlignment="Center">
<views:SettingsView/>
</TabItem>
</TabControl>
<Label Name="lblErrorInfo" Content="" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" ZIndex="10" Background="Red" Foreground="White" FontSize="34" FontWeight="Bold" IsVisible="false"></Label>
</Grid>
</Window>
SettingsView.axaml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:MyApp.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Views.SettingsView">
<this.DataContext>
<vm:SettingsViewModel/>
</this.DataContext>
<DataGrid Name="gEventlog" Items="{Binding EventlogData}" AutoGenerateColumns="False" CanUserResizeColumns="False" CanUserReorderColumns="False" Background="LightGray" CanUserSortColumns="True" Canvas.Top="20" Width="1024" Height="626" GridLinesVisibility="All" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" AlternatingRowBackground="Azure">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"/>
<DataGridTextColumn Header="Zeitstempel"/>
<DataGridTextColumn Header="Event-Typ"/>
<DataGridTextColumn Header="Benutzer"/>
<DataGridTextColumn Width="563" Header="Fehlermeldung"/>
<DataGridTextColumn Header="Funktion"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
SettingsViewModel.cs
using MyApp.Classes;
using Microsoft.Data.Sqlite;
using System;
using System.Data;
namespace MyApp.ViewModels
{
public class SettingsViewModel : ViewModelBase
{
// The appconfig class
private readonly AppConfiguration _appConfig;
// The utils class
private readonly Utils _utils;
// The eventlog class
private readonly Eventlog _event;
private string _importFilesPath;
// The data of the eventlog-grid
private DataView _eventlogData;
public DataView EventlogData
{
get { return _eventlogData; }
set
{
if (_eventlogData == value)
{
return;
}
_eventlogData = value;
}
}
public string ImportFilesPath
{
get { return _importFilesPath; }
set
{
if (_importFilesPath == value)
{
return;
}
_importFilesPath = value;
}
}
public SettingsViewModel()
{
// Initialize the members
_appConfig = new AppConfiguration();
_utils = new Utils();
_event = new Eventlog();
_eventlogData = new DataView();
_importFilesPath = "";
this.InitializeGUI();
}
private void InitializeGUI()
{
//Fill the eventlog grid
LoadEventlog();
_importFilesPath = _appConfig.ImportPath;
}
}
}
So my question is, how can I connect my UserControl with the corresponding ViewModel?
I'm new to AvaloniaUI, but coming from WPF where that approach (Give every UserControl an own ViewModel) works. Maybe I am overseeing some essential things.
Thanks in advance for every answer

If you have created your application using MVVM Avalonia template you should have a file called ViewLocator which already "connects" the view model with the corresponding view. Then if you want to display your settings in the main window you can add a property to the MainWindowViewModel:
public SettingsViewModel Settings { get; }
And bind it in the MainWindow
<ContentControl Content="{Binding Settings}"/>

Related

Why is my custom ContentControl with data triggers not showing up in the UI, though the constructor is called?

I'm using reflection and expression trees in C# to build a fairly adaptable search tool for our database. Because of this, I have a need for a custom ContentControl - termed 'MultiStyleInputBox' - which uses data triggers to adjust its ContentTemplate to the Type of input expected. The problem is, while the code builds just fine and I have confirmed that both the public and static constructors are being hit when the code executes, the ContentControl's content doesn't show up at all in my UI.
Now, I'm relatively new to writing custom XAML/C# UI control classes, but I have been able to cobble together the following:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
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:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Resources>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Resources>
</ContentControl>
And the code behind:
public sealed partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
I thought at one point that I might not have set the content of the ContentControl, and so I added a <ContentPresenter/>, but I received an error saying that the content is set more than once, so I believe that my <Style.Setters></Style.Setters> section is taking care of that. Otherwise, even running around using PresentationTraceSources.TraceLevel="High" on my bindings, I haven't so far been able to run into any useful errors.
Is there some sort of glaring issue in my code that I can immediately address (hopefully)? Do I need to reevaluate my approach to the problem?
Update
After suggested corrections in the answers below, here is the latest version of the code:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
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:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox, Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
And the code-behind:
public partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
Here is a test instantiation of the MultiStyleInputBox (I'm using Mahapps.Metro):
<Controls:MetroWindow
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
x:Class="MyApp.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp"
Title="Test Window" Height="450" Width="800"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<StackPanel>
<local:MultiStyleInputBox x:Name="TestMultiBox" Value="1" InputType="{x:Type sys:Int32}"/>
</StackPanel>
</Controls:MetroWindow>
When I try to instantiate this class, I'm still not getting anything showing up in my UI, and the ContentControl isn't taking up any space. Even if I include Width="50" Height="24", I still get nothing. I've tested setting both Value and InputType in code-behind and using a breakpoint to inspect the object, and I'm finding that, while both values get set, the Content of the ContentControl remains null.
The immediate problem is that your style isn't applied to the ContentControl you want to apply it to. You're defining an implicit ContentControl style which will be applied to any ContentControls you create in the content of this control -- but you aren't creating any, and that's not what you want anyhow.
For a quick fix, just change ContentControl.Resources to ContentControl.Style.
<ContentControl x:Class="MyApp.MultiStyleInputBox"
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:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
Your next problem will be that selecting a new DateTime in the DatePicker won't update a property bound to the Value property of your viewmodel. Here's the fix for that:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
And the last problem (or the first, perhaps) was that you weren't calling InitializeComponent() in the constructor, which is always required in any WPF codebehind class:
public MultiStyleInputBox()
{
InitializeComponent();
}

Show dialog in menu and prevent clicks not on dialog controls from closing the menu / dialog

I have a MenuItem.
When I click the item, I want the sub-MenuItem to open and show a form to login.
Hereunder what I already made (feel free to completely redesign this)...
The problem now is:
when I mouse-over or click in the subitem, the item is highlighted.
when i click on an item (except the textbox) the menu closes.
Thank you for your help!
<MenuItem>
<MenuItem.Header>
<Image
Width="16"
Height="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{StaticResource DisconnectedIcon}" />
<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding Connected}">
<Setter Property="Source" Value="{StaticResource ConnectedIcon}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</MenuItem.Header>
<autogrid:AutoGrid
Columns="Auto,Auto"
Margin="1"
RowHeight="25">
<autogrid:AutoGrid.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Width" Value="100" />
</Style>
<Style TargetType="{x:Type PasswordBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Width" Value="100" />
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="3" />
<Setter Property="Width" Value="100" />
</Style>
</autogrid:AutoGrid.Resources>
<TextBlock Text="System: "/>
<TextBox></TextBox>
<TextBlock Text="Username: "/>
<TextBox></TextBox>
<TextBlock Text="Password: "/>
<PasswordBox></PasswordBox>
</autogrid:AutoGrid>
<Button Content="Connect"/>
</MenuItem>
I recommend making your login controls part of a new window.
And for anyone else following along, you need to run Install-Package WpfAutoGrid -Version 1.4.0 in your package manager console.
For future reference, please post a MINIMAL and complete example.
Took me longer than it should have to throw this together.
Please read "How to create a Minimal, Complete, and Verifiable example"
Here's my MCVE example:
MainWindow.xaml:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:autogrid="clr-namespace:WpfAutoGrid;assembly=WpfAutoGrid"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="500">
<Window.DataContext>
<local:VM/>
</Window.DataContext>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem>
<MenuItem.Header>
<Image
Width="16"
Height="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{StaticResource DisconnectedIcon}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Connected}" Value="true">
<Setter Property="Source" Value="{StaticResource ConnectedIcon}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Connected}" Value="false">
<Setter Property="Source" Value="{StaticResource DisconnectedIcon}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</MenuItem.Header>
<MenuItem x:Name="connectMenuItem" Header="{Binding ConnectOrDisconnect}" Click="MenuItem_Click"/>
</MenuItem>
</Menu>
<Grid DockPanel.Dock="Bottom" Background="Black" />
</DockPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
VM thisDC = (VM)this.DataContext;
if (thisDC.Connected)
{
// DisConnect
thisDC.Connected = false;
}
else
{
ConnectWindow cw = new ConnectWindow
{
Owner = this,
DataContext = this.DataContext
};
cw.ShowDialog();
}
}
}
}
ConnectWindow.xaml:
<Window x:Class="WpfApp1.ConnectWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:autogrid="clr-namespace:WpfAutoGrid;assembly=WpfAutoGrid"
mc:Ignorable="d" Title="ConnectWindow" Height="180" Width="270">
<autogrid:AutoGrid
Columns="Auto,Auto"
Rows="Auto,Auto, Auto, Auto"
Margin="20"
RowHeight="25">
<autogrid:AutoGrid.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Width" Value="100" />
</Style>
<Style TargetType="{x:Type PasswordBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Width" Value="100" />
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="3" />
<Setter Property="Width" Value="100" />
</Style>
</autogrid:AutoGrid.Resources>
<TextBlock Text="System: "/>
<TextBox></TextBox>
<TextBlock Text="User Name: "/>
<TextBox></TextBox>
<TextBlock Text="Password: "/>
<PasswordBox></PasswordBox>
<Button Grid.ColumnSpan="2" Content="Connect" Click="Button_Click" />
</autogrid:AutoGrid>
</Window>
ConnectWindow.xaml.cs:
using System.Windows;
namespace WpfApp1
{
public partial class ConnectWindow : Window
{
public ConnectWindow()
{
InitializeComponent();
}
private bool verified = true;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (verified)
{
VM thisDC = (VM)this.DataContext;
thisDC.Connected = true;
this.Close();
}
else
{
// error message
this.Close();
}
}
}
}
VM.cs:
using System.ComponentModel;
namespace WpfApp1
{
class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool connected;
public bool Connected
{
get { return connected; }
set
{
{
connected = value;
OnPropertyChanged("Connected");
if (value) { ConnectOrDisconnect = "Disconnect"; }
else { ConnectOrDisconnect = "Connect"; }
}
}
}
private string connectOrDisconnect;
public string ConnectOrDisconnect
{
get { return connectOrDisconnect; }
set
{
connectOrDisconnect = value;
OnPropertyChanged("ConnectOrDisconnect");
}
}
public VM()
{
Connected = false;
}
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
App.xaml:
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
resources.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BitmapImage x:Key="ConnectedIcon" UriSource="pack://application:,,,/WpfApp1;component/ConnectedIcon.ico" />
<BitmapImage x:Key="DisconnectedIcon" UriSource="pack://application:,,,/WpfApp1;component/DisconnectedIcon.ico" />
</ResourceDictionary>

WPF Bind Background of ListView ItemContainerStyle

I have a UserControl (FahrtControl.xaml) with a ListView. This UserControl is bound to an ItemsControl in my MainWindow.xaml. The MainWindow has its own ViewModel and the FahrtControl has its own ViewModel. I now want to bind the Background of the Listview items to a Brush property in the ViewModel of FahrtControl.
Here are the relevant parts of my code:
MainWindow.xaml:
<Window x:Class="WpfFrontend.Forms.Main.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfFrontend.Forms.Main"
xmlns:fahrtControl="clr-namespace:WpfFrontend.Controls.FahrtControl"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type fahrtControl:FahrtControlViewModel}">
<fahrtControl:FahrtControl />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Fahrten, UpdateSourceTrigger=PropertyChanged}" />
MainViewModel.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
using System.Windows.Media;
using Backend;
using DatabaseCommunication;
using WpfFrontend.Annotations;
using WpfFrontend.Controls.FahrtControl;
namespace WpfFrontend.Forms.Main
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel ()
{
SyncFahrten ();
}
private void SyncFahrten ()
{
var fahrtenPromise =
MainUtility.GetFahrtenToRangeAsync (GlobalProperties.Start, GlobalProperties.Start.AddDays (6));
fahrtenPromise.Task.GetAwaiter ().OnCompleted (() =>
{
AddFahrten (fahrtenPromise.Task.Result);
});
}
private void AddFahrten (List <ExtendedFahrt> fahrten)
{
foreach (var fahrtControlViewModel in
fahrten.Select (fahrt =>
{
return new FahrtControlViewModel (
Brushes.Red, Brushes.Red, Brushes.White,
fahrt.Ort,
new ObservableCollection <string>
{
fahrt.Bemerkung
}, new ObservableCollection <KundeDisplay> (fahrt.Kunden));
}))
Fahrten.Add (fahrtControlViewModel);
OnPropertyChanged ("");
}
private ObservableCollection <FahrtControlViewModel> _fahrten =
new ObservableCollection <FahrtControlViewModel> ();
public ObservableCollection <FahrtControlViewModel> Fahrten
{
get => _fahrten;
set
{
if (Equals (value, _fahrten))
return;
_fahrten = value;
OnPropertyChanged ();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
}
}
FahrtControl.xaml:
<UserControl x:Class="WpfFrontend.Controls.FahrtControl.FahrtControl"
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:WpfFrontend.Controls.FahrtControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style x:Key="HeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</UserControl.Resources>
<ListView ItemsSource="{Binding Kunden}"
Background="{Binding KundenBrush, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="{Binding DataContext.KundenBrush}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource HeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" />
</GridView>
</ListView.View>
</ListView>
</UserControl>
FahrtControlViewModel.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Media;
using Backend;
using WpfFrontend.Annotations;
using WpfFrontend.Misc;
namespace WpfFrontend.Controls.FahrtControl
{
public class FahrtControlViewModel : INotifyPropertyChanged
{
private Brush _kundenBrush = Brushes.Red;
private ObservableCollection <KundeDisplay> _kunden;
/// <inheritdoc />
public FahrtControlViewModel (
Brush kundenBrush,
ObservableCollection <KundeDisplay> kunden)
{
Kunden = kunden;
KundenBrush = kundenBrush;
}
public Brush KundenBrush
{
get => _kundenBrush;
set
{
if (value.Equals (_kundenBrush))
return;
_kundenBrush = value;
KundenDark = _kundenBrush.IsDark ();
OnPropertyChanged ();
}
}
public ObservableCollection <KundeDisplay> Kunden
{
get => _kunden;
set
{
if (Equals (value, _kunden))
return;
_kunden = value;
OnPropertyChanged ();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
}
}
I've already tried the following:
How i change ListView Item Background Color according to listview item HarfNotu value in wpf
https://social.msdn.microsoft.com/Forums/vstudio/en-US/b25973bb-9e9c-4a99-8234-39a042e0a478/apply-styles-dynamically-to-buttons-in-xaml?forum=wpf
How do I set the background color of a listview item in WPF using databinding?
https://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplateselector%28v=vs.110%29.aspx
https://social.msdn.microsoft.com/Forums/vstudio/en-US/4df9f644-9bfa-4913-acc6-0bce711b70ec/setting-wpf-listview-background-when-disabled-and-empty?forum=wpf
Binding the value of a Setter Property in WPF
WPF Error 40 BindingExpression path error: property not found on 'object'
How to set background of listview?
And if I had to guess every other suggestion slightly related to the topic. What am I doing wrong here? Does it have something to do with the fact that I'm using a UserControl inside an ItemsControl? Other property bindings, not enclosed by a style tag, work inside the my UserControl, so it must have something to do with the style tag, doesn't it?
DataContext of ListView.ItemContainerStyle is not the same as ListView's. you can either find the correct data context with its element name:
<UserControl x:Class="WpfFrontend.Controls.FahrtControl.FahrtControl"
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:WpfFrontend.Controls.FahrtControl"
mc:Ignorable="d"
<!-- -->
x:Name="root"
<!-- -->
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style x:Key="HeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</UserControl.Resources>
<ListView ItemsSource="{Binding Kunden}"
Background="{Binding KundenBrush, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<!-- -->
<Setter Property="Background" Value="{Binding ElementName=root, Path=DataContext.KundenBrush}" />
<!-- -->
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource HeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" />
</GridView>
</ListView.View>
</ListView>
</UserControl>
Or by tracing up the element tree:
<UserControl x:Class="WpfFrontend.Controls.FahrtControl.FahrtControl"
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:WpfFrontend.Controls.FahrtControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style x:Key="HeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</UserControl.Resources>
<ListView ItemsSource="{Binding Kunden}"
Background="{Binding KundenBrush, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<!-- -->
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource AncestorType=FahrtControl}, Path=DataContext.KundenBrush}" />
<!-- -->
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource HeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" />
</GridView>
</ListView.View>
</ListView>
</UserControl>

Caliburn Micro User Control Data Context

I'm new to Caliburn Micro so I'm sure there's something easy that I' missing here.
I have a top-level Shell View:
<Window x:Class="LotRunPlotGrid.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:LotRunPlotGrid.Views"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<local:LotRunPlotGridView />
</Grid>
</Window>
and it's associated view model:
namespace LotRunPlotGrid
{
public class ShellViewModel : IShell {}
}
Then I have a user control defined as:
<UserControl x:Class="LotRunPlotGrid.Views.LotRunPlotGridView"
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:converter="clr-namespace:LotRunPlotGrid.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:LotRunPlotGrid.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="900"
d:DataContext="{d:DesignInstance Type=vm:LotRunPlotGridViewModel, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<converter:LotRunItemValueToColorConverter x:Key="ColorConverter"/>
<Style x:Key="LotRunButtonStyle" TargetType="Button">
<Setter Property="Width" Value="Auto"/>
<Setter Property="Height" Value="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Content" Value="{Binding LotID}"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row ="0" Text="Lot Run Plot Grid View" FontSize="20" FontFamily="Segoe UI"/>
<ItemsControl Grid.Row="1" ItemsSource="{Binding LotRunItemsCollection}" Margin="0,0,-200,0" HorizontalAlignment="Left" Width="893">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="10"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="LotRunItemButton" Style="{StaticResource LotRunButtonStyle}">
<Button.Background>
<MultiBinding Converter="{StaticResource ColorConverter}">
<Binding Path="LotRunDataDisplayMode" />
<Binding Path="LotRunItemValue"/>
</MultiBinding>
</Button.Background>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
and it's associated view model ....
using Caliburn.Micro;
using System.Collections.ObjectModel;
namespace LotRunPlotGrid.ViewModels
{
public class LotRunPlotGridViewModel : PropertyChangedBase
{
private ObservableCollection<LotRunItem> _lotRunItemsCollection = new ObservableCollection<LotRunItem>();
public ObservableCollection<LotRunItem> LotRunItemsCollection
{
get { return _lotRunItemsCollection; }
set { _lotRunItemsCollection = value; }
}
private int _numDisplayedColumns;
public int NumDisplayedColumns
{
get { return _numDisplayedColumns; }
set { _numDisplayedColumns = value; }
}
private int _numDisplayedRows;
public int NumDisplayedRows
{
get { return _numDisplayedRows; }
set { _numDisplayedRows = value; }
}
private int _lotRunDataDisplayMode;
public int LotRunDataDisplayMode
{
get { return _lotRunDataDisplayMode; }
set { _lotRunDataDisplayMode = value; }
}
public LotRunPlotGridViewModel()
{
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot1", LotRunItemValue = "55", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot2", LotRunItemValue = "45", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot3", LotRunItemValue = "35", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot4", LotRunItemValue = "25", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot5", LotRunItemValue = "15", LotRunItemColor = "#FF05579" });
}
}
}
The issue I'm having is that the Items Control does not show up because I get a binding error stating that the LotRunItemsCollection is not found in the ShellViewModel, when as displayed above, LotRunItemsCollection is a member of the LotRunPlotGridViewModel.
So what am I missing here regarding binding the LotRunPlotGridViewModel to the LotRunPlotGridView so that LotRunItemsCollection is found in the correct view model?
Thanks for any help!
You get that message because the control has no backing data context with that property name.
Create a property in the shell view model like below
namespace LotRunPlotGrid
{
public class ShellViewModel : PropertyChangedBase, IShell {
private LotRunPlotGridViewModel myGrid = new LotRunPlotGridViewModel();
public LotRunPlotGridViewModel MyGrid {
get { return myGrid; }
set {
myGrid = value;
NotifyOfPropertyChanged();
}
}
}
}
and then in the view you can name the user control to match the property name
<Window x:Class="LotRunPlotGrid.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:LotRunPlotGrid.Views"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<local:LotRunPlotGridView x:Name="MyGrid" />
</Grid>
</Window>
The framework will by convention Bind the MyGrid property to the local:LotRunPlotGridView as its data context.
If you do not want to tie the user control directly to the shell the framework is smart enough to look for the view based on the bound view model.
For example if the shell has the following
<Window x:Class="LotRunPlotGrid.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<ContentControl x:Name="MyGrid" />
</Grid>
</Window>
Note the local namespace was removed. The framework when binding the control will notice that the content is empty and search for the matching view of the bound property using naming conventions.

Why do my triggers end up with a blank control?

In WPF I'm trying to create a "flag" control that displays a checkmark or an X based on a bound dependency property (Flag)
<UserControl x:Name="Root" (Other user control stuff)>
<ContentControl Height="20" x:Name="flagHolder">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="False">
<Setter Property="Content" Value="{StaticResource XIcon}" />
<Setter Property="Foreground" Value="Crimson"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="True">
<Setter Property="Content" Value="{StaticResource CheckIcon}" />
<Setter Property="Foreground" Value="ForestGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UserControl>
On startup every icon is correct (I have several of these controls, each bound to different values). However, when I toggle a few (one that was "off" turns "on" and the one currently "on" turns "off") I see two things:
The control that was turned "on" has become a green check (as desired)
The control that was turned "off" is now just blank
Inspecting the visual tree seems to indicate that everything is working (though I could easily be missing something here), and the order of the triggers doesn't seem to matter. What am I doing wrong?
Here is an example icon, the path geometry is removed since its just noise:
<Viewbox x:Key="CheckIcon" x:Shared="False">
<Path Style="{StaticResource IconPathStyle}">
<Path.Data>
<PathGeometry Figures="Bunch of SVG" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
I'm unable to reproduce your issue, but here what I have and it's working:
App.xaml
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Viewbox x:Key="CheckIcon" x:Shared="False">
<Canvas Height="24" Width="32">
<Path Width="7.85446" Height="8.57578" Canvas.Left="-0.0522281" Canvas.Top="-0.100391" Stretch="Fill" StrokeThickness="1.04192" StrokeMiterLimit="2.75" Stroke="#FF000000" Data="F1 M 0.468732,4.66838L 3.03345,7.95443L 7.28127,0.420569"/>
</Canvas>
</Viewbox>
<Viewbox x:Key="XIcon" x:Shared="False">
<Canvas Height="24" Width="32">
<Path Data="M0,0 L1,1 M0,1 L1,0" Stretch="Fill" Stroke="Black" StrokeThickness="3" Width="12" Height="12" />
</Canvas>
</Viewbox>
</Application.Resources>
</Application>
YesNo.xaml
<UserControl x:Class="WpfApplication1.YesNo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Root">
<ContentControl Height="20" Name="flagHolder">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="False">
<Setter Property="Content" Value="{StaticResource XIcon}" />
<Setter Property="Foreground" Value="Crimson"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="True">
<Setter Property="Content" Value="{StaticResource CheckIcon}" />
<Setter Property="Foreground" Value="ForestGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UserControl>
YesNo.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class YesNo : UserControl
{
public YesNo()
{
InitializeComponent();
}
public static readonly DependencyProperty FlagProperty = DependencyProperty.Register(
"Flag", typeof(bool), typeof(YesNo), new PropertyMetadata(default(bool)));
public bool Flag {
get {
return (bool) GetValue(FlagProperty);
}
set {
SetValue(FlagProperty, value);
}
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="" Width="400" Height="400">
<StackPanel Orientation="Vertical" Margin="50">
<wpfApplication1:YesNo Flag="{Binding Flag1}"/>
<wpfApplication1:YesNo Flag="{Binding Flag2}"/>
<wpfApplication1:YesNo Flag="{Binding Flag2}"/>
<wpfApplication1:YesNo Flag="{Binding Flag1}"/>
<Button Content="Toggle" Click="ButtonBase_OnClick"></Button>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : INotifyPropertyChanged
{
private bool _flag1;
private bool _flag2;
public MainWindow()
{
InitializeComponent();
DataContext = this;
Flag1 = true;
Flag2 = false;
}
public bool Flag1 {
get {
return _flag1;
}
set {
_flag1 = value;
OnPropertyChanged();
}
}
public bool Flag2 {
get {
return _flag2;
}
set {
_flag2 = value;
OnPropertyChanged();
}
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) {
Flag1 = !Flag1;
Flag2 = !Flag2;
}
}
How it looks like:
Video: http://www.screencast.com/t/J5IY7DR3Ry

Categories

Resources