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>
Related
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}"/>
for my work I need to generate datagrids via code behind. The datagrids contain 2 columns: Questions (Just text, which should not be editable) and "Answers" which needs a textBox or comboBox based on an enum.
I tried to bind a list of questions (Containing the text and an answer field) to the datagrid. The question column works just fine. But the textboxes don't receive any value. I can type in them, but once I sort the datagrid all values are gone. In the itemsource of the data grid I can see that the values doesn't update at all :/
After this failure I tried this with normal datagrid in XAML but it doesn't work either. The column for answers is a DataGridTemplate Column.
Here is my xaml:
<Window x:Class="BindingTest.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:BindingTest"
xmlns:t="clr-namespace:BindingTest;assembly=BindingTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="AnswerTemp">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Typ}" Value="{x:Static t:QuestionType.Numeric}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Path=Answer,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGrid x:Name="MetaDataGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Questions" Binding="{Binding Text}">
</DataGridTextColumn>
<DataGridTemplateColumn Header="Answers" CellTemplate="{StaticResource ResourceKey=AnswerTemp}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code behind:
// Just contains a Dictionary <string,QuestionBlock> called SeperatorList
public static ActualPage actualPage = new ActualPage();
public MainWindow()
{
InitializeComponent();
QuestionBlock seperator = new QuestionBlock();
seperator.Questions = new ObservableCollection<Question>();
for (int counter = 1; counter < 11; counter++)
seperator.Questions.Add(new Question() { Text = $"What is the {counter}. Answer?", Answer = $"{5 + counter}", Typ = QuestionType.Numeric, Names = new List<string>() { "1", "2", "3" } });
actualPage.SeperatorList.Add("Test", seperator);
MetaDataGrid.ItemsSource = seperator.Questions;
}
Code in QuestionBlock:
public class QuestionBlock : INotifyPropertyChanged
{
private ObservableCollection<Question> _questionBlock;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Question> Questions
{
get => _questionBlock;
set
{
_questionBlock = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Questions)));
}
}
}
Code in Question:
public class Question : INotifyPropertyChanged
{
private string _text;
private string _answer;
private QuestionType _typ;
public event PropertyChangedEventHandler PropertyChanged;
public string Text
{
get => _text;
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
public string Answer
{
get => _answer;
set
{
_answer = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Answer)));
}
}
public QuestionType Typ
{
get => _typ;
set
{
_typ = value;
}
}
}
What am I doing wrong here? Note: The values that should be included in the comboBox are not enum values! There are values from a string list. Any help would be appreciated :)
You should put the editable control in the CellEditingTemplate of the column:
<DataGridTemplateColumn Header="Answers" CellEditingTemplate="{StaticResource AnswerTemp}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Answer}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...and bind the TextBox to the Answer property of the DataContext of the ContentControl:
<DataTemplate x:Key="AnswerTemp">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Typ}" Value="{x:Static t:QuestionType.Numeric}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Path=DataContext.Answer,
RelativeSource={RelativeSource AncestorType=ContentControl}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
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.
When the visibility of a CarViewControl is set to collapsed, it still shows a placeholder where it used to be (see screenshot below).
Is there any way to completely hide a ListViewItem when it is Collapsed?
XAML Code
<ScrollViewer>
<ListView ItemsSource="{Binding CarVM.UserCars}" ShowsScrollingPlaceholders="False">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<ctrl:CarViewControl Car="{Binding}" Visibility="{Binding HideCar, Converter={ThemeResource InverseVisConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
In the image above, there are three CarViewControls which are collapsed, followed by one which is not. One is highlighted. I want them to be completely invisible when the content is collapsed.
What I've tried:
Setting height of the DataTemplate control to 0 (just to see if it hides the placeholder which had no effect
Setting ShowsScrollingPlaceholders to False based on this documentation: MSDN ListView Placeholders
Reason For Collapse Requirement
Within each CarViewControl, a WebView exists which includes a security token (which maintains that the WebView is logged into a specific web site). If you try to pass the WebView by reference, due to what I can only assume are security measures, you lose that security token and must re-login to the site. That is why adding/removing the control from the ObservableCollection will not work in my case.
I would say your design is flawed, but I can't fix that; so I'll provide a "workaround."
The issue is that your DataTemplate is collapsing, which is great, but clearly the container it is in doesn't collapse. This won't happen inherently because the parent won't inherit from a child. First realization is every item is wrapped in a ListViewItem and you can observe that from setting your ItemContainerStyle. This leaves you with two solutions (workarounds). You can either set up some triggers on your ListViewItem or you can do something easier like I did--and if you don't mind the UI affects.
My full working application is below. The main point is that you have to edit the layout/behavior of the ListViewItem. In my example, the default values aren't BorderThickeness and Padding isn't "0, 0, 0, 0"... Setting those to 0 will get hide your items completely.
MainWindow.xaml
<Window x:Class="CollapsingListViewItemContainers.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:CollapsingListViewItemContainers"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"></BooleanToVisibilityConverter>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Disappear Car 3" Click="Button_Click" />
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="MinHeight" Value="0" />
<Setter Property="Padding" Value="0 0 0 0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:Car}">
<Grid Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisConverter}}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Id}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace CollapsingListViewItemContainers
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Car> _cars = new ObservableCollection<Car>
{
new Car("Not yours", "Mine", 1),
new Car("Not mine", "Yours", 2),
new Car("Not ours", "His", 3),
new Car("Not ours", "Hers", 4),
};
public ObservableCollection<Car> Cars
{
get
{
return _cars;
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Cars[2].IsVisible = !Cars[2].IsVisible;
}
}
public class Car : INotifyPropertyChanged
{
private bool _isVisible;
public bool IsVisible
{
get
{
return _isVisible;
}
set
{
_isVisible = value;
NotifyPropertyChanged("IsVisible");
}
}
public string Name
{
get; set;
}
public string Title
{
get; set;
}
public int Id
{
get; set;
}
public Car(string name, string title, int id)
{
Name = name;
Title = title;
Id = id;
IsVisible = true;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Edit
Let's be honest, the above was a pretty cheap solution and I wasn't satisfied with it after thinking about it for another 3 minutes. The reason I'm dissatisfied is because you can still select the item if you have access to a keyboard. In the example above, click the first item, "hide" the item(s), then use your mouse and the ListView.SelectedItem will still change.
So below is a quick solution (workaround :D ) to actually remove the item from the list and preventing them from getting focus. Replace the ListView.ItemContainerStyle with this one and change the ActualHeight trigger value to match the values you're seeing. This will change based on OS themes I believe--I'll leave it up to you to test. Lastly, remember the ListViewItem.DataContext is going to be that of an item in the ItemsSource. This means the DataTrigger bound to IsVisible is bound to the Car.IsVisible property.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<Trigger Property="ActualHeight" Value="4">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
Edit 2 (edited before I even posted the first edit.
Screw it, don't bind visibility of your CarViewControl; You don't need to. You solely need to focus on removing the item itself, and once the item is removed, the containing controls will be removed as well (though you should test this yourself and change IsTabStop and IsFocusable if you can still tab to items in CarViewControl). Also, since using an arbitrary number with the ActualHeight binding isn't very safe, just binding straight to the IsVisible property (or HideCar in your case) and triggering visibility of the ListViewItem should be sufficient.
Finally, here is my final XAML:
<Window x:Class="CollapsingListViewItemContainers.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:CollapsingListViewItemContainers"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"></BooleanToVisibilityConverter>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Disappear Car 3" Click="Button_Click" />
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:Car}">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Id}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Window>
Instead of attempting to hide item via the UI, I would remove the item from the View or Collection that the ItemsSource is bound against. It's a cleaner approach, and the UI never has to be concerned about Visibility of an item.
EDIT 1
When a user selects a specific car from the quick select menu, it shows that car and hides the others.
Makes sense; let's call that view "UserCars", and assume the ItemsSource is bound to UserCars.
How about this: change the ItemsSource to be bound to "SelectedCollection". When you want to show UserCars, point SelectedCollection to the UserCars collection.
To limit the set, you'd simply point SelectedCollection to a new SingleCar that you populate with only UserCars.SelectedItem
XAML:
<ListView ItemsSource="{Binding SelectedCollection}" SelectedItem="{Binding SelectedCar}">
ViewModel:
private Car _selectedCar;
public Car SelectedCar {
get { return _selectedCar; }
set { _selectedCar = value; OnPropertyChanged("SelectedCar"); }
}
private ObservableCollection<Car> _selectedCollection = CarVM.UserCars;
private ObservableCollection<Car> SelectedCollection {
get { return _selectedCollection; }
set { _selectedCollection = value; OnPropertyChanged("SelectedCollection"); }
}
private ObservableCollection<Car> _originalCollectionForReferenceKeepingOnly;
// called via RelayCommand
public void UserJustSelectedACar()
{
ObservableCollection<Car> singleItemCollection = new ObservableCollection<Car>();
singleItemCollection.Add(SelectedCar);
_originalCollectionForReferenceKeepingOnly = SelectedCollection;
SelectedCollection = singleItemCollection;
}
public void ReturnToFullUsedCarsList()
{
SelectedCollection = _originalCollectionForReferenceKeepingOnly;
}
EDIT 2
It appears that you are trying to make a ListView pretend to be a "Car Details" view by hiding these other items. This is inherently a bad idea; a different UI element bound to the Listview's Selected Car Item would make for a better solution. Since the new detail panel would just be looking at an already-generated Car instance, you wouldn't incur any data hit. Even if you can make this approach work right now, I'm worried you're going to just cause yourself more grief in the future.
I have a UserControl:
<UserControl x:Class="WpfApplication1.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Button Name="myButton" Content="hello world" Padding="10" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</UserControl>
and code-behind:
public partial class UserControl1 : UserControl
{
public bool IsShown
{
get { return (bool)GetValue(IsShownProperty); }
set { SetValue(IsShownProperty, value); }
}
public static readonly DependencyProperty IsShownProperty =
DependencyProperty.Register("IsShown", typeof(bool), typeof(UserControl1), new UIPropertyMetadata(false));
public UserControl1()
{
InitializeComponent();
}
}
I want to add an Effect to myButton when IsShown is true. In the MainWindow,
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
<Window.Resources>
<DropShadowEffect x:Key="outerGlow" Color="#00E300" Direction="0" ShadowDepth="0" BlurRadius="12" />
<Style x:Key="style" TargetType="my:UserControl1">
<Style.Triggers>
<Trigger Property="IsShown" Value="true">
<Setter Property="myButton.Effect" Value="{StaticResource outerGlow}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<my:UserControl1 x:Name="userControl11" Style="{StaticResource style}" />
</Window>
But VS cannot solve symbol myButton.
How to fix it?
Update:
I found the MSDN article. Can anyone tell me which category my property path falls in?
You can't do that.
If you need to affect a control which is inside the scope of a UserControl, you will need to define a property IN the UserControl and reference that in your inner control using RelativeSource or ElementName:
<UserControl x:Class="WpfApplication1.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="mycontrol">
<Button Name="myButton" Content="hello world" Padding="10" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsShown, ElementName="mycontrol"}" Value="True">
<Setter Property="Effect" Value"{StaticResource OrWhatever}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl>
When you need to set this effect in a Style that can be applied externally, you could create another property MyButtonEffect and set that in the Trigger.
public partial class UserControl1 : UserControl
{
public Effect MyButtonEffect
{
get { return (Effect)GetValue(MyButtonEffectProperty); }
set { SetValue(MyButtonEffectProperty, value); }
}
public static readonly DependencyProperty MyButtonEffectProperty =
DependencyProperty.Register("MyButtonEffect", typeof(Effect), typeof(UserControl1),
new FrameworkPropertyMetadata(null, MyButtonEffectPropertyChanged));
private static void MyButtonEffectPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((UserControl1)obj).myButton.Effect = (Effect)e.NewValue;
}
}
The Trigger:
<Trigger Property="IsShown" Value="true">
<Setter Property="MyButtonEffect" Value="{StaticResource outerGlow}"/>
</Trigger>