I'm currently teaching myself XAML/C# and writing a calendar application. Right now it's creating a grid and then applying user control elements to the grid. It is correctly building my calendar but, instead of defining the number of rows in XAML, I want to be able to set the number via C# dynamically. Some months use more or less weeks (March needs 5 but April needs 6). I'm wondering how to do that or if I should be using a different control than grid.
This is what the UI looks like.
XAML code
<UserControl x:Class="CMS.Control.MonthView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Background="AliceBlue">
<Image x:Name="MonthPrev" Source="/Images/Previous.png" Height="24" Margin="16,0,6,0" MouseLeftButtonUp="MonthPrev_MouseLeftButtonUp"/>
<Image x:Name="MonthNext" Source="/Images/Next.png" Height="24" Margin="6,0,16,0" MouseLeftButtonUp="MonthNext_MouseLeftButtonUp"/>
<Label x:Name="DateLabel" Content="January 2017" FontSize="16" FontFamily="Bold" VerticalAlignment="Center"/>
</StackPanel>
<Grid Grid.Row="1" Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="60" Width="*"/>
<ColumnDefinition MinWidth="60" Width="*"/>
<ColumnDefinition MinWidth="60" Width="*"/>
<ColumnDefinition MinWidth="60" Width="*"/>
<ColumnDefinition MinWidth="60" Width="*"/>
<ColumnDefinition MinWidth="60" Width="*"/>
<ColumnDefinition MinWidth="60" Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Sunday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
<Label Grid.Column="1" Content="Monday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
<Label Grid.Column="2" Content="Tuesday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
<Label Grid.Column="3" Content="Wednesday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
<Label Grid.Column="4" Content="Thursday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
<Label Grid.Column="5" Content="Friday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
<Label Grid.Column="6" Content="Saturday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
</Grid>
<Grid x:Name="WeekRowGrid" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
</Grid>
</UserControl>
C# Code
namespace CMS.Control
{
/// <summary>
/// Interaction logic for MonthView.xaml
/// </summary>
public partial class MonthView : UserControl
{
private DateTime _DispayDate;
public MonthView()
{
InitializeComponent();
_DispayDate = DateTime.Now;
DrawMonth();
}
//Generates the
private void DrawMonth()
{
DateTime FirstDayOfMonth = new DateTime(_DispayDate.Year, _DispayDate.Month, 1);
int DisplayFrontPadding = (int)FirstDayOfMonth.DayOfWeek; // # of days that need to be displayed before the 1st of the month
int DaysInDisplayMonth = DateTime.DaysInMonth(_DispayDate.Year, _DispayDate.Month);
int DaysInDisplay = DisplayFrontPadding + DaysInDisplayMonth;
DaysInDisplay += 7 - DaysInDisplay%7; // Rounds up the displayed days to a multiple of 7
DateLabel.Content = _DispayDate.ToString("MMMM") + " " + _DispayDate.Year;
for (int i = 0; i<DaysInDisplay; i++)
{
DateTime DisplayDay = FirstDayOfMonth.AddDays(i - DisplayFrontPadding);
DayBox DB = DayBox.GetDay(); // DayBox factory
DB.DrawDay(DisplayDay);
Grid.SetRow(DB, i / 7);
WeekRowGrid.Children.Add(DB);
Grid.SetColumn(DB, i%7);
}
}
//Generates a calendar for the previous month on button click.
private void MonthPrev_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_DispayDate = _DispayDate.AddMonths(-1);
DrawMonth();
}
//Generates a calendar for the next month on button click.
private void MonthNext_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_DispayDate = _DispayDate.AddMonths(1);
DrawMonth();
}
}
}
I know I should really be using MVVM but I'm still wrapping my brain around programming in MVVM pattern and want to get this working. I'll probably refactor it once I'm more comfortable with it.
I just wanted to finish this project
Understood. The thing is, the basic idea behind MVVM isn't really all that hard, and if you embrace it, you likely will finish the project faster, than if you continue to try to hard-code all your UI. I can't guarantee that, of course. But I've been through the same thing, and I can tell you that you can spend a lot of time fighting WPF trying to configure the UI in code-behind explicitly.
Without a good Minimal, Complete, and Verifiable code example to start with, it wasn't practical for me to replicate exactly your user interface. But here is a simple code example that shows the basic approach you might take to use MVVM to build the UI you want…
First, it's helpful to have a base class that implements INotifyPropertyChanged for you. It simplifies the view model boilerplate a lot:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
_OnPropertyChanged(propertyName);
}
}
protected virtual void _OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then, we'll need view models. In this UI, there are two basic components: the overall view, and the individual days of the month. So, I made a view model for each:
class DateViewModel : NotifyPropertyChangedBase
{
private int _dayNumber;
private bool _isCurrent;
public int DayNumber
{
get { return _dayNumber; }
set { _UpdateField(ref _dayNumber, value); }
}
public bool IsCurrent
{
get { return _isCurrent; }
set { _UpdateField(ref _isCurrent, value); }
}
}
and…
class MonthViewViewModel : NotifyPropertyChangedBase
{
private readonly ObservableCollection<DateViewModel> _dates = new ObservableCollection<DateViewModel>();
private DateTime _selectedDate;
public DateTime SelectedDate
{
get { return _selectedDate; }
set { _UpdateField(ref _selectedDate, value); }
}
public IReadOnlyCollection<DateViewModel> Dates
{
get { return _dates; }
}
protected override void _OnPropertyChanged(string propertyName)
{
base._OnPropertyChanged(propertyName);
switch (propertyName)
{
case nameof(SelectedDate):
_UpdateDates();
break;
}
}
private void _UpdateDates()
{
_dates.Clear();
DateTime firstDayOfMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1),
firstDayOfNextMonth = firstDayOfMonth.AddMonths(1);
int previousMonthDates = (int)firstDayOfMonth.DayOfWeek; // assumes Sunday-start week
int daysInView = previousMonthDates + DateTime.DaysInMonth(SelectedDate.Year, SelectedDate.Month);
// round up to nearest week multiple
daysInView = ((daysInView - 1) / 7 + 1) * 7;
DateTime previousMonth = firstDayOfMonth.AddDays(-previousMonthDates);
for (DateTime date = previousMonth; date < firstDayOfNextMonth; date = date.AddDays(1))
{
_dates.Add(new DateViewModel { DayNumber = date.Day, IsCurrent = date == SelectedDate.Date });
}
for (int i = 1; _dates.Count < daysInView; i++)
{
_dates.Add(new DateViewModel { DayNumber = i, IsCurrent = false });
}
}
}
As you can see, so far there's been no mention of UI, and yet already all the logic exists to build a month's worth of dates. The UI part, the XAML, will have no idea that you are doing anything related with months or dates. The closest it gets is a hard-coded invariant, i.e. the number of days in a week which are used to control the number of columns in the UniformGrid that will display your data.
The XAML looks like this:
<Window x:Class="TestSO43147585CalendarMonthView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="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:l="clr-namespace:TestSO43147585CalendarMonthView"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
SizeToContent="Height"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MonthViewViewModel SelectedDate="{x:Static s:DateTime.Today}"/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:MonthViewViewModel}">
<ItemsControl ItemsSource="{Binding Dates}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True" Columns="7"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
<DataTemplate DataType="{x:Type l:DateViewModel}">
<Border BorderBrush="Black" BorderThickness="0, 0, 1, 0">
<StackPanel>
<TextBlock Text="{Binding DayNumber}">
<TextBlock.Style>
<p:Style TargetType="TextBlock">
<Setter Property="Background" Value="LightBlue"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</TextBlock.Style>
</TextBlock>
<Grid Height="{Binding ActualWidth, RelativeSource={x:Static RelativeSource.Self}}"/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}" VerticalAlignment="Top"/>
</Grid>
</Window>
The XAML does three things:
It declares a MonthViewViewModel object to be used as the DataContext for the Window. An object in the visual tree, i.e. children of the Window, will inherit their parent's context if they have none of their own.
It declares data templates for the two view models. These tell WPF how it should visually represent the data. A view model contains the data that you want to represent, and this data is referenced in the template via {Binding...} syntax. In many cases (e.g. text, numbers, enum values), you can just bind directly and the default conversion will do what you want (as is the case above). If not, you can implement your own IValueConverter and incorporate that in the binding.
It provides a place for the MonthViewViewModel to be displayed, by declaring a ContentControl, where the content of that control is bound simply to the current data context (a {Binding} expression without a path will bind to the source, and the default source is the current data context).
In the context of the ContentControl, as well as the individual items being displayed in the ItemsControl, WPF will search for the template that is appropriate for the data object defined for that context, and will automatically populate your visual tree, binding to the necessary properties, according to that object.
There are a number of advantages to this approach, the primary ones being that you can describe your UI instead of having to code it, and that you maintain the OOP principle of "separation of concerns", which is key in reducing the mental work-load involved by allowing you to focus on one thing at a time, instead of having to deal with UI and data logic together.
A couple of side-notes regarding the XAML above:
You might notice that I have added the p: XML namespace and used it for the Style element. This is only work around a Stack Overflow bug, in which the Style element by itself confuses the XML formatter and prevents the element and its children from being formatted correctly. The XAML will compile fine like this, but it's not actually necessary in real code. In your regular XAML, you can safely omit it.
I included a feature your code didn't, just for the purpose of illustration. That is, the current date is highlighted in yellow. The technique shown here is very useful, as it allows you to customize the appearance of an item in a single template, based on property values of the view model. But there is a little trap: in WPF, if you explicitly set an element's property via the attribute syntax, e.g. something like <TextBlock Text="{Binding DayNumber}" Background="LightBlue">, then that syntax will take precedence over any <Setter...> elements in a style. You have to remember to set the default value of any property that you intend to set via a trigger, in its own <Setter...> in the style as well (as shown above).
You could create a method that adjust the number of rows automatically based on the number of weeks (rows). This method always remove all the rows and then add the correct number of rows you need.
private void AdjustRowDefinitions(int numberOfWeeks)
{
WeekRowGrid.RowDefinitions.Clear();
for (int i = 0; i < numberOfWeeks; i++)
{
RowDefinition rowDef = new RowDefinition();
rowDef.Height = new GridLength(1, GridUnitType.Star); //this sets the height of the row to *
WeekRowGrid.RowDefinitions.Add(rowDef);
}
}
Related
I am working on a WPF Project with Caliburn Micro. Please consider the following code snippets:
PopupWindowView.xaml
<Window x:Class="PTSRDesktopUI.Views.PopupWindowView"
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:PTSRDesktopUI.Views"
mc:Ignorable="d" ShowInTaskbar="True"
ResizeMode="NoResize" SizeToContent="Height" FontSize="24"
MouseDown="Window_MouseDown" Background="#FFF7F7F7" Icon="/Images/infoicon_8Ve_icon.ico"
Title="Pfad Info" Height="300" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock FontSize="20" Grid.Column="1" Text="{Binding Path=Path}" Margin="10 10 10 10"
HorizontalAlignment="Center" VerticalAlignment="Center" IsEnabled="False"
FontFamily="Segoe UI Light"/>
</Grid>
</Window>
PopupWindowViewModel.cs
using Caliburn.Micro;
using PTSRDesktopUI.Models;
namespace PTSRDesktopUI.ViewModels
{
public class PopupWindowViewModel : Screen
{
private string _path;
public PopupWindowViewModel(ChangesModel model)
{
_path = model.ParameterPfad;
}
public string Path
{
get { return _path; }
set
{
_path = value;
NotifyOfPropertyChange(() => Path);
}
}
}
}
OverviewView.xaml
<DataGridTemplateColumn CellStyle="{StaticResource DataGridCellCentered}" Header="Info">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button BorderThickness="0" Height="30" Width="30"
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
cal:Message.Attach="ShowPath($this)">
<Image Source="/Images/infoicon.png"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
OverviewViewModel.cs
private readonly IWindowManager manager = new WindowManager();
public void ShowPath(ChangesModel model)
{
manager.ShowWindow(new PopupWindowViewModel(model), null, null);
}
All this does is, when the Button in the DataGrid is pressed, the PopupWindow pops up and displays some data. It all works fine. The only thing I cannot seem to figure out is how to display the new PopupWindow at the same location where the clicked Button is, or rather where my mouse pointer is. I have read all the other StackOverflow questions regarding this problem but none of them does the trick for me. I either get an error or it just does not work. Anyone has any ideas how to solve this?
Hi fellow programmers,
I'm working on a WPF software that uses a Canvas to display and move graphic objects.
When the user clic on an object, I need to display a panel with the selected object's properties.
These properties are different for each object, one can have a displayed text, another a background color or a scale value.
What is the best way to program this ?
I have 9 objects type, I'm searching for something more elegant than creating my controls in panels and switch betwenn then for every graphic object type.
Thank you for your help.
Edit - to show design code :
The dock panel for generated Wpf controls to display properties.
<DockPanel x:Name="pnlProperties" Width="200" Grid.Column="2" Background="red">
<Grid x:Name="GridProperties" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Margin="0,2,0,25" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="95"/>
<RowDefinition Height="95"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ***** Label ***** -->
<Label x:Name="lblLabel1" Content="test Prop" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel2" Content=" Prop 2" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="1" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel3" Content=" Prop 3" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="2" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel4" Content=" Prop 4" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="3" Grid.Column="0" FontSize="16"/>
</Grid>
</Grid>
</DockPanel>
The Canvas that displays the MovableObject (userControl) of each graphic objects :
<UserControl x:Class="DashEditor.Views.ScreenView"
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:DashEditor.Views">
<Canvas x:Name="ObjectsCanvas" HorizontalAlignment="Center" VerticalAlignment="Center" Width="800" Height="480" AllowDrop="True" Background="Black" PreviewMouseLeftButtonDown="ObjectsCanvas_PreviewMouseLeftButtonDown" >
<Image x:Name="imgFond" Stretch="Fill" Source="/DashEditor;component/assets/FondXAP.png" Width="800" Height="480"/>
</Canvas>
One of the graphic object class :
[StructLayout(LayoutKind.Sequential)]
[XmlRoot("XapLabel")]
public class XapLabel : IXapGraphicObject
{
private int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
[XmlIgnore]
private MovableObject _Control;
[XmlIgnore]
public MovableObject Control
{
get
{
return _Control;
}
set
{
_Control = value;
}
}
private Point _pos;
public Point Pos
{
get
{
return _pos;
}
set
{
_pos = value;
}
}
public IXapGraphicObject getXapParent(MovableObject Control)
{
return this;
}
public ObjectType Type
{
get
{
return ObjectType.Label;
}
}
public XapLabel()
{
}
public void ConnectToMoveEvent()
{
_Control.OnObjectTranslating += _Control_OnObjectTranslating;
}
private void _Control_OnObjectTranslating(Vector displacement)
{
Pos = Pos + displacement;
}
}
I've done something similar to this, if you are familiar with MVVM:-
For the canvas, I used an ItemsControl bound to an ObservableCollection of your "graphic objects", to which you'll be adding objects that you want to appear on the canvas. You'll also need to change the ItemsControl's panel template to a Canvas:-
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="800" Height="480" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Your "graphic object" classes need to expose double properties (say "X" and "Y"), to control the object's position on the canvas.
Next, create a XAML DataTemplate for each of these classes, to define their visual appearance. The data template should include the following bindings:
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
For the property grid, rather than write your own, look at the free Xceed Toolkit community edition (here), which has a very good PropertyGrid control. You bind its SelectedObject property to the selected object, but read the documentation - there are plenty of decent features.
(If you are using MVVM then remember to change your classes to implement INotifyPropertyChanged, and raise the PropertyChanged event in the setters).
For the drag and drop functionality, you should just be able to set the selected object's X and Y values within the mouse move event.
Not a full solution I know, but will hopefully point you in the right direction.
In our C# WPF application (with the Caliburn.Micro framework) we have a View and a ViewModel. In the ViewModel we have a string-property and I want to show this string inside each child of an ItemsControl (these items have their own ViewModel). I know I could just pass the property to each of these items, but that shouldn't be needed.
So, here is the relevant part of the ViewModel:
using System;
...
namespace NatWa.MidOffice.Modules.Financien.Views
{
public class BankgarantieFinancienOpsplitsenViewModel : ValidationBase<BankgarantieFinancienOpsplitsenViewModel>
{
...
public BankgarantieFinancienOpsplitsenViewModel(BankgarantieFinancienState state, ...)
{
...
Dossiernummer = state.Dossiernummer;
Kopers = state.Kopers.Select(k =>
{
var bfkvm = new BankgarantieFinancienKoperViewModel(k, adresService);
bfkvm.ObservePropertyChanged(koper => koper.Bedrag).Subscribe(p => CalculateOpenstaandBedrag());
return bfkvm;
}).ToList();
...
}
public string Dossiernummer
{
get { return _dossiernummer; }
private set
{
if (value == _dossiernummer) return;
_dossiernummer = value;
NotifyOfPropertyChange(() => Dossiernummer);
}
}
...
}
}
The relevant part of the View:
<Window x:Class="NatWa.MidOffice.Modules.Financien.Views.BankgarantieFinancienOpsplitsenView"
...>
<Grid Style="{StaticResource WindowPaddingStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
...
</Grid.RowDefinitions>
...
<ItemsControl x:Name="Kopers" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{StaticResource BorderBrush}" BorderThickness="1" Margin="0,3" Padding="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
...
<StackPanel Grid.Column="1" Orientation="Vertical">
...
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="0,3" HorizontalAlignment="Right">
...
<!-- THIS IS WHERE I WANT TO DISPLAY THE DOSSIERNUMMER-PROPERTY OF THE PARENT VIEWMODEL -->
<TextBlock Text="{Binding ??Parent??.Dossiernummer}"/>
...
</StackPanel>
...
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
...
</Grid>
</Window>
I did try to replace the TextBox with the following, based on this SO-answer, but to now result:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Dossiernummer}"/>
I've also tried to add the DataContext binding to the Window (even though Caliburn.Micro should do this automatically):
<Window x:Class="NatWa.MidOffice.Modules.Financien.Views.BankgarantieFinancienOpsplitsenView"
...
DataContext="{Binding}">
Ok, problem found.. >.>
Me and a co-worker tried a couple of more things, like:
Adding this to the window:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignData BankgarantieFinancienOpsplitsenViewModel}" mc:Ignorable="d"
Changing the TextBlock-binding to:
<TextBlock Text="{Binding ElementName=Kopers, Path=DataContext.Dossiernummer}"/>
and a couple of more things, all to no avail.. In a couple of other Views we've used the exact same things with success, so we had no idea what could be wrong. And then it struck us..
So, what was the problem? The string in the property was null for the ViewModel I was testing this for.... Great start of the day to make such a stupid mistake.. So, we've changed setting the value in the ViewModel to this:
Dossiernummer = state.Dossiernummer ?? state.UbizzDossiernummer;
(The UbizzDossiernummer is the number of the old system we are replacing (which we've imported into our appliction), and the Dossiernummer are the new numbers from Objects made in our application. The object I've been testing this for was an imported one..)
So, I've changed it back to:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Dossiernummer}"/>
and it works..
I'm using a slider control in a C# WPF application and want to offer the ability for users to invert the control on the fly.
I've already established that the slider control has a built in IsDirectionReversed property which works well for changing the direction of the control (effectively swapping its minimum and maximum values to opposite ends of the control). However, when the property is changed on the fly, the thumb position remains where it is (visually implying an incorrect value) but its value remains the same (as I would expect).
(After inverting the control, clicking anywhere on the track causes the thumb to update to either +1 or -1 of its current value but repositions the thumb into its correct visual position)
Can anyone suggest a way of forcing the thumb to update its position when the inverted property is changed?
XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="139" Width="303">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Slider Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Name="SliderControl" HorizontalAlignment="Left" VerticalAlignment="Top" Width="276" IsDirectionReversed="{Binding Inverted}" Maximum="100" Minimum="1" SmallChange="1"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="Value:" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="{Binding ElementName=SliderControl, Path=Value}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="Inverted:" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBlock Grid.Row="3" Grid.Column="2" Text="{Binding ElementName=SliderControl, Path=IsDirectionReversed}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button Grid.Row="4" Grid.Column="2" Content="Flip!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
C#
public partial class MainWindow : Window, INotifyPropertyChanged
{
public bool mSliderInverted = false;
public bool Inverted
{
get
{
return mSliderInverted;
}
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
mSliderInverted = !mSliderInverted;
OnPropertyChanged("Inverted");
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
It seems flipping the IsDirectionInversed property doesn't cause the control to rerender, I'd say this is a bug in WPF (Bug in WPF!? No way!).
By trial and error, I discovered that if you invalidate the PART_Track part of the slider (The element that contains the RepeatButtons and the Thumb), it magically switches it around and everything works the way it should!
So if you just modify your Button_Click handler to look like this, everything should work just fine. :-)
private void Button_Click(object sender, RoutedEventArgs e)
{
mSliderInverted = !mSliderInverted;
OnPropertyChanged("Inverted");
// Retrieve the Track from the Slider control
var track = SliderControl.Template.FindName("PART_Track", SliderControl) as Track;
// Force it to rerender
track.InvalidateVisual();
}
I have a XAML with 2 columns in a Grid and I have a button that when I click it, in the code behind, I set the visibility to collapse, and want to resize the other half of the screen to try to take up the whole screen. The collapsing part works, and the RHS then shifts over to the LHS, but it does not take up the entire screen. I tried using both the Auto and Star to resize in HidePlots, but it never takes the full screen. I thought if I collapsed the LHS, and set the column to * for the RHS, it would take up the whole screen. Any thoughts? Thanks.
Here's some code to make it more clear:
<Grid Grid.Row="1" x:Name="ExpandableGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="1.5*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" x:Name="TableGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Grid.Column="0" x:Name="SampleViewGroupBox" Header="SampleView" HorizontalAlignment="Stretch" FontFamily="Arial" FontSize="12" Margin="5,0,5,0" >
<ContentControl Content="{Binding LayoutManager.SampleView}" Height="Auto" Width="Auto"/>
</GroupBox>
<Button x:Name="TableButton" HorizontalAlignment="Right" Content="Button" Width="15" Height="15" VerticalAlignment="Top" Margin="0,0,-2,0" Click="MaxButton_Click" Grid.Column="0" Grid.Row="0"/>
</Grid>
<Grid Grid.Column="1" x:Name="BaseViewGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<GroupBox Grid.RowSpan="2" Grid.Column="1" Name="BaseViewGroupBox" Header="PLOTS" Margin="5,0,5,0" >
<ContentControl Content="{Binding LayoutManager.ConsensusView}" Height="Auto" Width="Auto" />
</GroupBox>
</Grid>
</Grid>
private void MaxButton_Click(object sender, RoutedEventArgs e)
{
UIElement senderElement = (UIElement)sender;
if (_tableMinimized)
{
HideTables(false);
_tableMinimized = false;
((Button)senderElement).Style = (Style)FindResource("DashboardDetailsButton");
}
else
{
HideTables(true);
_tableMinimized = true;
((Button)senderElement).Style = (Style)FindResource("DashboardDetailsButtonReverse");
}
}
private void HideTables(bool hide)
{
if (hide)
{
foreach (UIElement child in TableGrid.Children)
child.Visibility = Visibility.Collapsed;
for (int i = 0; i < ExpandableGrid.ColumnDefinitions.Count; i++)
ExpandableGrid.ColumnDefinitions[i].Width = GridLength.Auto;
ExpandableGrid.ColumnDefinitions[1].MinWidth = 500;
for (int i = 0; i < ExpandableGrid.RowDefinitions.Count; i++)
ExpandableGrid.RowDefinitions[i].Height = GridLength.Auto;
TableButton.Visibility = Visibility.Visible;
}
else
{
foreach (UIElement child in TableGrid.Children)
child.Visibility = Visibility.Visible;
for (int i = 0; i < ExpandableGrid.ColumnDefinitions.Count; i++)
ExpandableGrid.ColumnDefinitions[i].Width = new GridLength(1, GridUnitType.Star);
for (int i = 0; i < ExpandableGrid.RowDefinitions.Count; i++)
ExpandableGrid.RowDefinitions[i].Height = new GridLength(1, GridUnitType.Star);
}
}
Edit: I tried to also change one line to:
ExpandableGrid.ColumnDefinitions[1].MinWidth = System.Windows.SystemParameters.PrimaryScreenWidth-20;
instead of the hard-coded 500 value, it looks correct. However, if I try to click the button again to revert back to normal, the RHS takes up the bulk of the screen without getting back to its original position.
Your current column definition says to make Column B equal to 1.5 times the size of Column A, so even if ColumnB's content is hidden, the column will still take up 3/5 of the screen.
Change it so the column that collapses has a Width="Auto", and set it's Content's Width equal to whatever size it should be when it's expanded. If you want to keep the 1.5* default width, I'd recommend using something like a MathConverter to figure out what size it should be based on the parent Grid's width. I have the code for one posted here
<Grid x:Name="ParentGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid x:Name="RHS" Grid.Column="0" />
<!-- Collapse this Grid -->
<Grid x:Name="LHS" Grid.Column="1"
Width="{Binding ElementName=ParentGrid, Path=ActualWidth,
Converter={StaticResource MathConverter},
ConverterParameter=((#VALUE/5)*3)}" />
</Grid>
You need to set column 0 to be whatever you desire (Auto, 150, etc...) and set column 1 to be *.
It looks like your Grid is also within a Grid, so the parent's behavior also has to be taken into account.