ObservableCollection with ModelView not changed - c#

I use the MVVM Pattern but now I have a problem with the ObservableCollection in my WorkspaceViewModel.
I want to create from a "NavigationView" a TabItem in my "WorkspaceView". This is not the problem with Commands and an EventAggregator.
Then I had the problem the TabItem lost all informations because after TabItem_Changed the ViewModel was recreated. This was fixed with an Extended TabControl. Here
ViewModel: WorkspaceViewModel
public class WorkspaceViewModel : ViewModelBase.ViewModelBase
{
public WorkspaceViewModel()
{
this.TabItems = new ObservableCollection<ViewModelBase.ViewModelBase>();
CreateTabItemEvent.Instance.Subscribe(CreateTabItem);
}
#region Properties
private ViewModelBase.ViewModelBase _SelectedTabItem;
public ViewModelBase.ViewModelBase SelectedTabItem
{
get { return _SelectedTabItem; }
set { SetProperty(ref _SelectedTabItem, value); }
}
private ObservableCollection<ViewModelBase.ViewModelBase> _TabItems;
public ObservableCollection<ViewModelBase.ViewModelBase> TabItems
{
get { return _TabItems; }
set { SetProperty(ref _TabItems, value); }
}
#endregion
#region Methodes
private void CreateTabItem(string pObjectName)
{
//create TabItem and add it to the list
ViewModelBase.ViewModelBase newTabItem = null;
//Test Objects from Buttonnames
if (pObjectName.EndsWith("1"))
{
newTabItem = new CustomerViewModel();
}
else if (pObjectName.EndsWith("2"))
{
newTabItem = new ContactViewModel();
}
if (newTabItem != null)
{
this.TabItems.Add(newTabItem);
this.SelectedTabItem = newTabItem;
//Test Refresh - not work because list is "old"
RaisePropertyChanged(nameof(this.TabItems));
}
}
#endregion
}
View: WorkspaceView
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewmodel:CustomerViewModel}">
<view:CustomerView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:ContactViewModel}">
<view:ContactView/>
</DataTemplate>
<Style TargetType="{x:Type tabControlEx:TabControlEx}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid Background="{TemplateBinding Background}" ClipToBounds="True" KeyboardNavigation.TabNavigation="Local" SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0" />
<ColumnDefinition x:Name="ColumnDefinition1" Width="0" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto" />
<RowDefinition x:Name="RowDefinition1" Height="*" />
</Grid.RowDefinitions>
<DockPanel Margin="2,2,0,0" LastChildFill="False">
<TabPanel x:Name="HeaderPanel" Margin="0,0,0,-1" VerticalAlignment="Bottom" Panel.ZIndex="1" DockPanel.Dock="Left"
IsItemsHost="True" KeyboardNavigation.TabIndex="1" />
</DockPanel>
<Border x:Name="ContentPanel" Grid.Row="1" Grid.Column="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
KeyboardNavigation.DirectionalNavigation="Contained" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local">
<Grid x:Name="PART_ItemsHolder" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Workspace Control"></TextBlock>
<tabControlEx:TabControlEx x:Name="tbWorkspace" Grid.Row="1"
ItemsSource="{Binding TabItems}" SelectedItem="{Binding SelectedTabItem}"
IsSynchronizedWithCurrentItem="True" >
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}"/>
</Style>
</TabControl.ItemContainerStyle>
</tabControlEx:TabControlEx>
</Grid>
ViewModel: CustomerViewModel
public class CustomerViewModel : ViewModelBase.ViewModelBase
{
public CustomerViewModel()
{
this.SendMessageCommand = new DelegateCommand<string>(SendMessage);
this.Title = "Customer";
}
#region Commands
private ICommand _SendMessageCommand;
public ICommand SendMessageCommand
{
get { return _SendMessageCommand; }
set { SetProperty(ref _SendMessageCommand, value); }
}
#endregion
#region Methodes
private void SendMessage(string pMessage)
{
//Test to change the Title on the TabItem
this.Title = pMessage;
this.EditModus = false;
MessageSentEvent.Instance.Publish(pMessage);
}
#endregion
}
Now if I click on the button which is use the Command "SendMessageCommand" the Title is changed but the TabItem Header is not changed. The Title is every time "Customer" and the EditMode = true.
After this I look at the ObservableCollection in the WorkspaceViewModel... There is no changes. It has the "Title = Customer" and "EditMode = true"
So why the changes from the CustomerViewModel not apply the the WorkspaceViewModel.ObservableCollection?
Can someone help me?
Sample Project
EDIT:
ViewModel: ViewModelBase
public class ViewModelBase : BindableBase
{
public ViewModelBase()
{
this.Title = String.Empty;
this.EditModus = true;
}
#region Properties
private string _Title;
public string Title
{
get { return _Title; }
set { SetProperty(ref _Title, value); }
}
private bool _EditModus;
public bool EditModus
{
get { return _EditModus; }
set { SetProperty(ref _EditModus, value); }
}
#endregion
}
all ViewModels inherits from ViewModelBase and BindableBase from Prism 7 has INotifyPropertyChanged
EDIT 2: SOLUTION
I found the error. If I create a new TabItem, I create a new ViewModel too. But in the XAML I create a new ViewModel too. So the View hasnt the same ViewModel as the ObservableCollection in the WorkspaceViewModel.
So I delete the DataContext in the XAML and now it works.
<UserControl x:Class="CustomerModul.View.CustomerView"
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:CustomerModul.View"
xmlns:vm="clr-namespace:CustomerModul.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:CustomerViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<CheckBox Grid.Row="0" IsChecked="{Binding EditModus}">EditModus aktiv</CheckBox>
<Button Grid.Row="1" Command="{Binding SendMessageCommand}" CommandParameter="Gespeichert" Content="Save"></Button>
</Grid>

Related

WindowChrome causing issues with view ContentControl binding?

I've been working on a small tool for ISO integration and I decided to use WPF for the UI. I'm also doing my best to follow the MVVM pattern for future projects. For the main window, I decided to use WindowChrome so I could keep resizing with a restyled window, however, when I try to select a different view, it causes a exception. I tested it and it does work fine, but as soon as I try to bind the content to the templates content, it seems to cause issues with changing the current view.
Main window xaml:
<WindowChrome.WindowChrome>
<WindowChrome ResizeBorderThickness="{Binding ResizeBorderThickness}"
CaptionHeight="{Binding TitleHeight}" GlassFrameThickness="0"/>
</WindowChrome.WindowChrome>
<Window.Resources>
<Style TargetType="{x:Type local:OSToolWPF}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Padding="{Binding OuterMarginThickness}">
<Grid>
<Border CornerRadius="{Binding WindowCornerRadius}"
Background="{StaticResource BackgroundLightBrush}">
<Border.Effect>
<DropShadowEffect ShadowDepth="0" Opacity=".2"/>
</Border.Effect>
</Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding TitleHeightGridLength, FallbackValue=50}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border CornerRadius="10 10 0 0"
Background="{StaticResource BackgroundDarkBrush}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Viewbox Margin="25 0 0 0" HorizontalAlignment="Left">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Title}"/>
</Viewbox>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Style="{StaticResource ButtonBase}" Command="{Binding Minimize}" Width="50">
<Image Style="{StaticResource ControlImage}" Source="/Assets/Top/Minimize.png" Stretch="None"/>
</Button>
<Button Style="{StaticResource ButtonBase}" Command="{Binding Maximize}" Width="50">
<Image Style="{StaticResource ControlImage}" Source="/Assets/Top/Windowed.png" Stretch="None"/>
</Button>
<Button Style="{StaticResource ButtonBase}" Command="{Binding Close}" Width="50">
<Image Style="{StaticResource ControlImage}" Source="/Assets/Top/Close.png" Stretch="None"/>
</Button>
</StackPanel>
</Grid>
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel>
<Button Style="{StaticResource ButtonBase}" Tag="Registry" Command="{Binding Menu}"
Content="Registry" FontSize="20" Height="50" CommandParameter="{Binding
RelativeSource={RelativeSource Mode=Self}, Path=Tag}"/>
<Button Style="{StaticResource ButtonBase}" Tag="Services" Command="{Binding Menu}"
Content="Services" FontSize="20" Height="50" CommandParameter="{Binding
RelativeSource={RelativeSource Mode=Self}, Path=Tag}"/>
<Button Style="{StaticResource ButtonBase}" Tag="Visuals" Command="{Binding Menu}"
Content="Visuals" FontSize="20" Height="50" CommandParameter="{Binding
RelativeSource={RelativeSource Mode=Self}, Path=Tag}"/>
</StackPanel>
<Button Grid.Row="2" Style="{StaticResource ButtonBase}"
Content="Exit" FontSize="20" Command="{Binding Close}"
Height="50"/>
</Grid>
<Border Grid.Column="1">
<ContentControl Content="{TemplateBinding Content}"/>
<Border/>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ContentControl Content="{Binding CurrentView}"/>
Main window view model (the data binding is done in xaml.cs since it needs a window):
public class OSToolViewModel : BaseViewModel
{
#region Private
private Window wWindow;
private object wCurrentView;
private int wOuterMarginSize = 10;
private int wWindowRadius = 15;
#endregion
public OSToolViewModel(Window window)
{
wWindow = window;
wWindow.StateChanged += (sender, e) =>
{
OnPropertyChanged(nameof(ResizeBorderThickness));
OnPropertyChanged(nameof(OuterMarginSize));
OnPropertyChanged(nameof(OuterMarginThickness));
OnPropertyChanged(nameof(WindowRadius));
OnPropertyChanged(nameof(WindowCornerRadius));
};
Menu = new BaseCommand(e => CurrentView = new ExpanderItemsList());
Minimize = new BaseCommand(e => wWindow.WindowState = WindowState.Minimized);
Maximize = new BaseCommand(e => wWindow.WindowState ^= WindowState.Maximized);
Close = new BaseCommand(e => CloseMessage());
}
#region Voids
private void CloseMessage()
{
if (DialogBox.Show("Would you like to exit?", "", wWindow.Title, DialogBoxButtons.YesNo) == DialogResult.Yes)
{
wWindow.Close();
}
}
#endregion
#region Commands
public BaseCommand Menu { get; set; }
public BaseCommand Minimize { get; set; }
public BaseCommand Maximize { get; set; }
public BaseCommand Close { get; set; }
#endregion
#region Public
public object CurrentView
{
get { return wCurrentView; }
set
{
if (value == wCurrentView)
return;
wCurrentView = value;
OnPropertyChanged(nameof(CurrentView));
}
}
public int ResizeBorder { get; set; } = 6;
public int OuterMarginSize
{
get
{
return wWindow.WindowState == WindowState.Maximized ? 0 : wOuterMarginSize;
}
set
{
wOuterMarginSize = value;
}
}
public int WindowRadius
{
get
{
return wWindow.WindowState == WindowState.Maximized ? 0 : wWindowRadius;
}
set
{
wWindowRadius = value;
}
}
public double TitleHeight { get; set; } = 50;
public Thickness ResizeBorderThickness { get { return new Thickness(ResizeBorder + OuterMarginSize); } }
public Thickness OuterMarginThickness { get { return new Thickness(OuterMarginSize); } }
public CornerRadius WindowCornerRadius { get { return new CornerRadius(WindowRadius); } }
public GridLength TitleHeightGridLength { get { return new GridLength(TitleHeight + ResizeBorder); } }
#endregion
}
The view I'm trying to bind to is a page, so I'm not sure if that would cause issues or not, but I've tried a lot of different combos and the combo I shared causes a exception. If I didn't bind to to the templated content, it would work fine. I'm baffled as to why this happens, and as a side note, the buttons in the window aren't cornered like they should be, so if you have a solution for this too, that would be much appreciated! Thank you and I hope you enjoy your day!
I just realized this issue was because of the fact I was using a Page rather than a UserControl. Switching the view to a UserControl solved the issue. Hopefully this helps if anyone else runs into this issue.

LiveCharts derived user control with data binding displays no chart

I am creating a C# based WPF demonstration application that features a user defined control (User Control) "card" that features two labels and a chart (LiveCharts).
How do I correctly have the data bound to the LiveCharts control inside the card user control so the chart displays?
I have tried a variety of possible solutions including the DataContext={Binding RelativeSource={RelativeSource Self}}.
Without using the user control, the chart displays correctly, but as I have more than one instance required, I wish to have a user control for reuse.
Picture: Sample of current application when running
Repository
https://github.com/PWA-GouldA/C4Prog-DotNet-WPF-LiveChartDemo
Code Extract:
Interaction logic for CardLineChart.xaml
public partial class CardLineChart : UserControl
{
public CardLineChart()
{
InitializeComponent();
CardGrid.DataContext = this;
}
#region SeriesData DP
public SeriesCollection SeriesData
{
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("SeriesData",
typeof(SeriesCollection), typeof(CardLineChart),
new PropertyMetadata(new SeriesCollection()));
#endregion
#region BackgroundColour DP
public string BackgroundColour
{
set { SetValue(ColourBG, value); }
}
public static readonly DependencyProperty ColourBG =
DependencyProperty.Register("BackgroundColour",
typeof(string), typeof(CardLineChart), new PropertyMetadata(""));
#endregion
#region BottomLabel DP
public string BottomLabel
{
set { SetValue(LabelAtBottom, value); }
}
public static readonly DependencyProperty LabelAtBottom =
DependencyProperty.Register("BottomLabel",
typeof(string), typeof(CardLineChart), new PropertyMetadata(null));
#endregion
#region TopLabel DP
public string TopLabel
{
set { SetValue(LabelAtTop, value); }
}
public static readonly DependencyProperty LabelAtTop =
DependencyProperty.Register("TopLabel",
typeof(string), typeof(CardLineChart), new PropertyMetadata(null));
#endregion
}
CardLineChart.xaml
<UserControl x:Class="WPF_With_LiveCharts.CardLineChart"
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:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="200"
>
<Grid x:Name="CardGrid" Margin="5,5,5,5" MaxHeight="200" MaxWidth="200">
<Grid.Effect>
<DropShadowEffect BlurRadius="15" Direction="-90" RenderingBias="Quality" Opacity=".2" ShadowDepth="2"/>
</Grid.Effect>
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=Border1}" />
</Grid.OpacityMask>
<Grid.Resources>
<Style TargetType="lvc:LineSeries">
<Setter Property="StrokeThickness" Value="1"></Setter>
<Setter Property="Stroke" Value="White"></Setter>
<Setter Property="Fill" Value="#00ffffff"></Setter>
<Setter Property="PointGeometrySize" Value="0"></Setter>
<Setter Property="LineSmoothness" Value="0.25"></Setter>
</Style>
<Style TargetType="lvc:Axis">
<Setter Property="ShowLabels" Value="False"></Setter>
<Setter Property="IsEnabled" Value="False"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="60"></RowDefinition>
<RowDefinition Height="80*"></RowDefinition>
</Grid.RowDefinitions>
<Border x:Name="Border1" Grid.Row="0" Grid.RowSpan="3" CornerRadius="5"
Background="{Binding Path=BackgroundColour}" />
<TextBlock Grid.Row="0" TextAlignment="Center" Padding="5, 5, 0, 5"
Foreground="#ccFFFFFF" FontSize="12"
Text="{Binding Path=TopLabel}" />
<lvc:CartesianChart Grid.Row="1"
Margin="0,0,0,0"
Series="{Binding Path=SeriesData}"
Hoverable="False"
DataTooltip="{x:Null}">
<lvc:CartesianChart.AxisX>
<lvc:Axis MinValue="0"></lvc:Axis>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
<TextBlock Grid.Row="2" x:Name="ChartValue"
Foreground="#ccFFFFFF" FontSize="48"
VerticalAlignment="Center" TextAlignment="Center"
Margin="8,0,8,6" Text="{Binding Path=BottomLabel}"/>
</Grid>
</UserControl>
WindowMain.cs
public partial class MainWindow : Window
{
public SeriesCollection theData;
public SeriesCollection theData2;
public string BackgroundColour;
public MainWindow()
{
InitializeComponent();
theData = new SeriesCollection
{
new LineSeries
{
Values = new ChartValues<decimal> { 7, 3, 2, 3, 5, 7, 4 }
}
};
theData2 = new SeriesCollection
{
new LineSeries
{
Values = new ChartValues<decimal> { 100, 90, 70, 40, 10 }
}
};
BackgroundColour = "#FFCE2156";
DataContext = this;
}
private void ButtonLineChart_Click(object sender, RoutedEventArgs e)
{
}
}
WindowMain.xaml
<Window x:Class="WPF_With_LiveCharts.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:WPF_With_LiveCharts"
xmlns:control = "clr-namespace:WPF_With_LiveCharts"
mc:Ignorable="d"
Title="MainWindow" Height="460" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="45"/>
<RowDefinition Height="200"/>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="200*"/>
</Grid.ColumnDefinitions>
<Button x:Name="ButtonLineChart" Content="Line Chart" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75" Click="ButtonLineChart_Click" Height="25"/>
<Button x:Name="ButtonPieChart" Content="Pie Chart" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75" Grid.Column="2" Height="25"/>
<control:CardLineChart
Grid.Column="0" Grid.Row="1"
x:Name="TestingCardLineChart"
SeriesData="{Binding theData}"
BackgroundColour="#ffc22735"
TopLabel="Testing"
BottomLabel="123"
/>
<control:CardLineChart
Grid.Column="1"
Grid.Row="1"
x:Name="TestingCardLineChart2"
SeriesData="{Binding theData2}"
BackgroundColour="#FF000000"
TopLabel="Oh Yes!"
BottomLabel="9999"
/>
</Grid>
</Window>
Binding doesn't work on fields in WPF. If you change the series collection on MainWindow.xaml.cs to use properties instead you should be able to see the graphs.
eg
public SeriesCollection theData { get; set; }
public SeriesCollection theData2 { get; set; }

Can I make this block of XAML into a reusable "control"?

I have a Grid, and in that grid, I have this:
<StackPanel Grid.Row="2"
Grid.Column="0">
<Grid x:Name="GridButtonItem" Margin="30,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background"
Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
Value="#332a8dd4" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="False">
<Setter Property="Background"
Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Margin="3"
Source="{dx:DXImageOffice2013 Image=Windows_32x32.png}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Margin="10,3,3,0"
Text="Application Log" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Margin="10,0,3,3"
Text="C:\Program Files (x86)\ATI Technologies\ATI.ACE\MOM-InstallProxy" />
</Grid>
</StackPanel>
The StackPanel is actually meant to hold many of the GridButtonItem items. Is there a way that I can somehow make a "template" of GridButtonItem and then for each one I want to add to the StackPanel, just set the Image and Text properties?
Something like this (just pseudo-code for demonstration):
<StackPanel>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
<Grid Template="myGridItemTemplate">
<Setter Property="Image" Value="img1.png"/>
<Setter Property="Text1" Value="button1 Text"/>
<Setter Property="Text2" Value="button2 Text"/>
</Grid>
</StackPanel>
So each one that is added picks up the row/column definitions, and an embedded Image and two TextBlocks. Then I just set the three properties for each one added.
Is this possible?
You can put your grid control into a UserControl and then reuse the UserControl throughout your project. I have a simple example of doing this with a label and Textbox.
here is the XAML:
<UserControl x:Class="TestVision.CustomControls.LabelAndTextbox"
x:Name="parent"
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:TestVision.CustomControls"
mc:Ignorable="d" >
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=parent}">
<TextBlock Text="{Binding Path=Label}" Width="{Binding Path=LabelWidth}" VerticalAlignment="Center" TextAlignment="Right" Margin="0,0,10,0" Height="22"/>
<TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" Width="{Binding Path=TextboxWidth}" IsReadOnly="{Binding Path=TextboxReadOnly, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="{Binding Path=TextboxHorizontalContentAlgnment}"/>
</StackPanel>
</UserControl>
Any properties that you want to be able to set e.g. your image text etc. must be bound to Dependency Properties in the code behind.
Code behind:
public partial class LabelAndTextbox : UserControl
{
/// <summary>
/// Gets or sets the Label which is displayed next to the field
/// </summary>
public String Label
{
get { return (String)GetValue(LabelContent); }
set { SetValue(LabelContent, value); }
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContent =
DependencyProperty.Register("Label", typeof(string),
typeof(LabelAndTextbox), new PropertyMetadata(""));
public object Text
{
get { return (object)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(object),
typeof(LabelAndTextbox), new PropertyMetadata(null));
public Double LabelWidth
{
get { return (Double)GetValue(LabelWidthProperty); }
set { SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register("LabelWidth", typeof(Double),
typeof(LabelAndTextbox), new PropertyMetadata());
public Double TextboxWidth
{
get { return (Double)GetValue(TextboxWidthProperty); }
set { SetValue(TextboxWidthProperty, value); }
}
public static readonly DependencyProperty TextboxWidthProperty =
DependencyProperty.Register("TextboxWidth", typeof(Double),
typeof(LabelAndTextbox), new PropertyMetadata());
public bool TextboxReadOnly
{
get { return (bool)GetValue(TextboxReadOnlyProperty); }
set { SetValue(TextboxReadOnlyProperty, value); }
}
public static readonly DependencyProperty TextboxReadOnlyProperty =
DependencyProperty.Register("TextboxReadOnly", typeof(bool),
typeof(LabelAndTextbox), new FrameworkPropertyMetadata());
public HorizontalAlignment TextboxHorizontalContentAlgnment
{
get { return (HorizontalAlignment)GetValue(TextboxHorizontalContentAlgnmentProperty); }
set { SetValue(TextboxHorizontalContentAlgnmentProperty, value); }
}
public static readonly DependencyProperty TextboxHorizontalContentAlgnmentProperty =
DependencyProperty.Register("TextboxHorizontalContentAlgnment", typeof(HorizontalAlignment),
typeof(LabelAndTextbox), new FrameworkPropertyMetadata());
public LabelAndTextbox()
{
InitializeComponent();
}
}
you then will need to add a reference in the XAML file to your UserControl like this:
xmlns:Resource="clr-namespace:ProjectNamespace.FolderContainingYourControl"
Resource is a generic identifier you can call it what you like, you can then reference your control in the like this:
<Resource:LabelAndTextblock x:Name="AddressLine1" Label="{Binding LblTxt_AddressLine1}" Text="{Binding AddressLine1, Mode=TwoWay}" Margin="10,5,0,5" LabelWidth="70" TextWidth="250" TextHeight="60"/>
You could do this with a UserControl (two different ways) or a DataTemplate. Let's go with DataTemplate, because stuicidle already ably demonstrated one UserControl approach.
There are a couple of different ways to do this with a DataTemplate, too.
We're going to do something called an implicit DataTemplate. It's created in Resources, but it has no x:Key property, just a DataType="{x:Type local:GridItemViewModel}" property. What that will do is this: Wherever that DataTemplate is in scope, whenever XAML needs to display a GridItemViewModel and nothing is specifying a template to display it in, it'll use that implicit template.
Clear as mud! Welcome to the XAML learning curve.
ViewModels.cs
using System;
using System.ComponentModel;
using System.Windows.Media;
namespace GridItemAnswer
{
#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class
#region GridItemViewModel Class
public class GridItemViewModel : ViewModelBase
{
#region LabelText Property
private String _labelText = null;
public String LabelText
{
get { return _labelText; }
set
{
if (value != _labelText)
{
_labelText = value;
OnPropertyChanged();
}
}
}
#endregion LabelText Property
#region Path Property
private String _path = null;
public String Path
{
get { return _path; }
set
{
if (value != _path)
{
_path = value;
OnPropertyChanged();
}
}
}
#endregion Path Property
#region ImageSource Property
private ImageSource _imageSource = null;
public ImageSource ImageSource
{
get { return _imageSource; }
set
{
if (value != _imageSource)
{
_imageSource = value;
OnPropertyChanged();
}
}
}
#endregion ImageSource Property
}
#endregion GridItemViewModel Class
}
MainWindow.xaml
<Window
x:Class="GridItemAnswer.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:GridItemAnswer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<DataTemplate DataType="{x:Type local:GridItemViewModel}">
<StackPanel>
<Grid x:Name="GridButtonItem" Margin="30,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#332a8dd4" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Margin="3"
Source="{Binding Image}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="10,3,3,0"
Text="{Binding LabelText}"
/>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="10,0,3,3"
Text="{Binding Path}"
/>
</Grid>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ItemsControl>
<local:GridItemViewModel
LabelText="Foo Bar"
Path="c:\foo\bar"
/>
<local:GridItemViewModel
LabelText="Baz Planxty"
Path="c:\baz\planxty"
/>
</ItemsControl>
<Label>
<local:GridItemViewModel
LabelText="A frog walks into a bank asking for a loan"
Path="c:\knick\knack"
/>
</Label>
</StackPanel>
</Grid>
</Window>

WPF Custom Control based on grid or nested grid?

I'm learning WPF but I have a lot of Windows Forms background. I want to convert a WinForms custom control in which I did put a label and a textbox (making a TextField), with a property allowing to set percentage of width allocated to the label.
Now, in WPF, I'm a bit lost. Should I create a custom control that inherits from a grid and expose (how ?) the columns definition properties, or should I create a custom control that will "contain" a grid, and expose two properties "LabelWidth" and "ContentWidth", and bind the two column definitions to these properties ? (Thinking these properties would contain 1* and 3*).
Could someone show me an example of such construction to have a place to start?
You could create a UserControl with two dependency properties.
Please refer to the following sample code.
MyUserControl.xaml:
<UserControl x:Class="WpfApplication3.MyUserControl"
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:WpfApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LabelWidth, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<ColumnDefinition Width="{Binding ContentWidth, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Grid.ColumnDefinitions>
<TextBlock Text="..." />
<TextBox Grid.Column="1" />
</Grid>
</UserControl>
MyUserControl.xaml.cs:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register("LabelWidth", typeof(System.Windows.GridLength),
typeof(MyUserControl));
public System.Windows.GridLength LabelWidth
{
get { return (System.Windows.GridLength)GetValue(LabelWidthProperty); }
set { SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty ContentWidthProperty =
DependencyProperty.Register("ContentWidth", typeof(System.Windows.GridLength),
typeof(MyUserControl));
public System.Windows.GridLength ContentWidth
{
get { return (System.Windows.GridLength)GetValue(ContentWidthProperty); }
set { SetValue(ContentWidthProperty, value); }
}
}
Sample usage:
<local:MyUserControl LabelWidth="1*" ContentWidth="5*" />
Dependency Properties Overview: https://msdn.microsoft.com/en-us/library/ms752914(v=vs.110).aspx
I think I managed to achieve what I wanted to do by understanding mm8's code, in particular RelativeSource={RelativeSource AncestorType=UserControl} :
Added a custom control.
FieldText.cs :
public class FieldText : Control
{
static FieldText()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FieldText), new FrameworkPropertyMetadata(typeof(FieldText)));
}
public FieldText()
{
}
public static readonly DependencyProperty LabelLengthProperty =
DependencyProperty.Register("LabelLength", typeof(GridLength),
typeof(FieldText), new UIPropertyMetadata(new GridLength(25, GridUnitType.Star)));
public virtual GridLength LabelLength
{
get { return (GridLength)GetValue(LabelLengthProperty); }
set { SetValue(LabelLengthProperty, value); }
}
public static readonly DependencyProperty ContentLengthProperty =
DependencyProperty.Register("ContentLength", typeof(GridLength),
typeof(FieldText), new UIPropertyMetadata(new GridLength(75, GridUnitType.Star)));
public virtual GridLength ContentLength
{
get { return (GridLength)GetValue(ContentLengthProperty); }
set { SetValue(ContentLengthProperty, value); }
}
}
Generic.xaml :
<Style TargetType="{x:Type controls:FieldText}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:FieldText}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="grd" Margin="3px">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Path=LabelLength, RelativeSource={RelativeSource AncestorType=Control}}" />
<ColumnDefinition Width="{Binding Path=ContentLength, RelativeSource={RelativeSource AncestorType=Control}}" />
</Grid.ColumnDefinitions>
<Label x:Name="label" Grid.Column="0" Content="Field:" />
<TextBox x:Name="textbox" Grid.Column="1" MaxLines="1" TextWrapping="NoWrap" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Sample usage:
<controls:FieldText x:Name="fld1" LabelLength="25*" ContentLength="75*" />

OK button in dialog not enabling

I am building a WPF application using MVVM Light. In it I have a dialog box. The XAML:
<Window x:Class="ParserEditor.NewParserDialog"
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:ParserEditor"
xmlns:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
DataContext="{Binding NewParser, Source={StaticResource Locator}}"
Title="New Parser..."
SizeToContent="Height"
Width="300">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
<BitmapImage x:Key="ErrorImage" UriSource="Resources/Error.png" />
<local:BooleanToVisibilityConverter x:Key="BoolToVisiblity" True="Visible" False="Collapsed" />
<ControlTemplate x:Key="InputErrorTemplate">
<DockPanel LastChildFill="True">
<Image DockPanel.Dock="Right"
Height="16"
Margin="5"
Source="{StaticResource ErrorImage}"
ToolTip="Contains invalid data"
VerticalAlignment="Center"
Width="16" />
<Border BorderBrush="Red"
BorderThickness="2">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
<Style TargetType="ComboBox">
<Setter Property="Margin" Value="5,4,26,4" />
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource InputErrorTemplate}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{x:Static RelativeSource.Self}" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Name="PromptLabel"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Text="{Binding Path=Prompt, Mode=TwoWay}"
Visibility="{Binding Path=HasPrompt, Converter={StaticResource BoolToVisiblity}}"/>
<TextBlock Name="ParserTypeLabel"
Grid.Column="0"
Grid.Row="2"
Text="Parser Type:" />
<ComboBox Name="ParserTypePicker"
Grid.Column="1"
Grid.Row="2"
ItemsSource="{Binding Path=ParserTypes}"
SelectedItem="{Binding Path=ParserType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<Grid Name="ButtonGrid"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Name="OkButton"
Command="{Binding CloseCommand, ValidatesOnDataErrors=True}"
Grid.Column="0"
Content="OK"
IsDefault="True" />
<Button Name="CancelButton"
Grid.Column="1"
Content="Cancel"
IsCancel="True" />
</Grid>
</Grid>
</Window>
The View Model object:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using ParserEditor.Model;
namespace ParserEditor.ViewModel {
public class NewParserViewModel : ViewModelBase, IDataErrorInfo {
private readonly IDataService _dataService;
public string ParserType {
get { return _ParserType; }
set { Set( ref _ParserType, value ); }
}
private string _ParserType;
public ObservableCollection<string> ParserTypes { get; private set; }
public bool HasPrompt {
get { return !string.IsNullOrWhiteSpace( Prompt ); }
}
public string Prompt {
get { return _Prompt; }
set {
Set( ref _Prompt, value );
RaisePropertyChanged( nameof( HasPrompt ) );
}
}
private string _Prompt;
#region CloseCommand
public RelayCommand CloseCommand { get; private set; }
private bool CanCloseDialog() {
return ParserType == DataService.AWK_FORMAT ||
ParserType == DataService.CSHARP_FORMAT ||
ParserType == DataService.REGEX_FORMAT;
}
private void CloseDialog() {
Messenger.Default.Send( new CloseWindowMessage() );
}
#endregion
#region IDataErrorInfo Implementation
public string Error {
get { return this[ "ParserType" ]; }
}
public string this[ string columnName ] {
get {
switch ( columnName ) {
case "ParserType":
return string.IsNullOrWhiteSpace( ParserType ) ? "You must choose a Parser Type" : null;
default:
return null;
}
}
}
#endregion
public NewParserViewModel( IDataService dataService ) {
_dataService = dataService;
CloseCommand = new RelayCommand( CloseDialog, CanCloseDialog );
ParserTypes = new ObservableCollection<string>();
ParserTypes.Add( DataService.AWK_FORMAT );
ParserTypes.Add( DataService.CSHARP_FORMAT );
ParserTypes.Add( DataService.REGEX_FORMAT );
}
}
}
I've placed a breakpoint in the CanCloseDialog method and it only gets hit once, when the dialog is first displayed. If I select a choice in the ComboBox after the dialog is displayed, the OK button doesn't enable.
What am I missing?
I did some more searching & I finally found the answer here. It turns out I had to change the
using GalaSoft.MvvmLight.Command;
statement to
GalaSoft.MvvmLight.CommandWpf;
Doing this, everything works properly.
Try a delegate Func wich call CanCloseDialog instead.
Somehow RelayCommand lose pointer to the method whithout a delegate.
Invoke the delegate in the CanExecute method of the RelayCommand implementation.
Something like :
CloseCommand = new RelayCommand( CloseDialog,() => {return <your condition logic>});

Categories