ProgressBar.Value strangely affects other element's position - wpf

Unless I'm missing something basic, this appears to be a bug. It is simple to reproduce. Just create a new WPF application project and paste the following in MainWindow's XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="100" Width="525"
xmlns:local="clr-namespace:WpfApplication1">
<Grid>
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="IsTabStop" Value="False" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:AudioFile}">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="3" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Width="24" Height="24" VerticalAlignment="Center" HorizontalAlignment="Left">
<Image.Source>
<BitmapImage UriSource="{Binding Icon}" />
</Image.Source>
</Image>
<TextBlock Grid.Column="1" Text="{Binding FullPath}" VerticalAlignment="Center" />
<ProgressBar Grid.Column="1" Opacity=".3" Foreground="LightGreen" Value="{Binding UploadProgress}" />
<TextBlock Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="8">
<Run Text="{Binding UploadRate, StringFormat={}{0:00}}" />
<Run> kbps</Run>
</TextBlock>
<Image Grid.Column="2" Width="16" Height="16" VerticalAlignment="Center" />
<ProgressBar Grid.Row="1" Grid.ColumnSpan="3" Value="{Binding PercentCompleted, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Items>
<local:AudioFile FullPath="C:\audio1.mp3" Status="Uploading" UploadProgress="0" PercentCompleted="40" ErrorUploading="" />
</ListBox.Items>
</ListBox>
</Grid>
</Window>
Add a new class named AudioFile and paste the following code therein:
Public Class AudioFile
Public Property FullPath() As String
Public Property UploadProgress() As Integer
Public Property UploadRate() As Single = 120
Public Property Status() As String
Public Property ErrorUploading() As String
Public ReadOnly Property Icon() As String
Get
If String.IsNullOrEmpty(FullPath.Trim()) Then
Return ""
Else
Dim RetVal As String = "Resources/speaker.png"
Select Case System.IO.Path.GetExtension(FullPath).ToLower().Trim("."c)
Case "mp3"
RetVal = "Resources/mp3.png"
Case "wav"
RetVal = "Resources/wav.png"
End Select
Return RetVal
End If
End Get
End Property
Public Property PercentCompleted() As Integer
End Class
Now go to XAML and change the value of PercentCompleted from "40" to "80". Note the position of FullPath TextBlock as it jumps to further right. Why? The only binding we have for PercentCompleted is of ProgressBar's Value.

Related

How to Bind Textbox.ReadOnly to property in the parent (owner) view model

It seems a resolved question but after trying several answers posted here I could not resolve my problem. Here it is:
I want to set (by binding) the property IsReadonly of a TextBox to a property of the main view model which contains the type to which the TextBox is already bound to. Also de text box is in a DataTemplate bound to a type.
Trying this produces "Property 'IsReadOnly' is not found" which has sense because the data template is bound to a type that doesn't have it:
<TextBox Name="PromoEntryForm" AutomationProperties.Name="Promo Description" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FriendlyName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding Path=IsReadOnly}" />
Trying this produces nothing, which also has sense because it is binding to the same TextBox.IsReadOnly, as I understand:
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=IsReadOnly}" />
I know I have to bind to a property in the parent (container view model) so I also tried this, which produces "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='CataloGo.ViewModel.PromoViewModel', AncestorLevel='1''. BindingExpression:Path=IsReadOnly; DataItem=null; target element is 'TextBox' (Name='PrintableNameEntryForm'); target property is 'IsReadOnly' (type 'Boolean')"
<TextBox Name="PrintableNameEntryForm" AutomationProperties.Name="Printable Name" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=PublicName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type localVm:PromoViewModel}}, Path=IsReadOnly}" />
But the type is the one in the error message and the DataContext of the view is set to an instance of PromoViewModel so I don't know why it cannot find the source.
This is the full view model:
public class PromoViewModel : SupervisedEntity
{
private ObservableCollection<Promo> _promos;
private Promo _current;
private bool _editMode;
private bool _isNew;
public string Title => "Promociones";
public ObservableCollection<Promo> Promos
{
get => this._promos;
set => this.SetPropertyValue(ref this._promos, value);
}
public Promo Current
{
get => this._current;
set => this.SetPropertyValue(ref this._current, value);
}
public bool IsEditMode
{
get => this._editMode;
set
{
this.SetPropertyValue(ref this._editMode, value);
this.NotifyPropertyChanged(nameof(this.IsReadOnly));
this.NotifyPropertyChanged(nameof(this.HideIfEditing));
this.NotifyPropertyChanged(nameof(this.ShowIfEditing));
}
}
public bool IsNew
{
get => this._isNew;
set => this.SetPropertyValue(ref this._isNew, value);
}
public Promo Backup { get; set; }
public bool IsReadOnly => !this.IsEditMode;
public Visibility HideIfEditing => (this.IsReadOnly ? Visibility.Visible : Visibility.Collapsed);
public Visibility ShowIfEditing => (this.IsReadOnly ? Visibility.Collapsed : Visibility.Visible);
}
And this is the corresponding view:
<Window x:Class="CataloGo.View.PromoWindow"
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:localroot="clr-namespace:CataloGo"
xmlns:local="clr-namespace:CataloGo.View"
xmlns:localVm="clr-namespace:CataloGo.ViewModel"
xmlns:localModel="clr-namespace:CataloGo.Model"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance localVm:PromoViewModel, d:IsDesignTimeCreatable=True}"
Title="{Binding Path=Title}" SizeToContent="WidthAndHeight" MinWidth="640" MinHeight="480">
<Window.Resources>
<CollectionViewSource
Source="{Binding Path=Promos}"
x:Key="PromoListingDataView" />
<DataTemplate x:Key="PromoDetailTemplate" DataType="{x:Type localModel:Promo}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="106" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Style="{StaticResource SmallTitleStyle}"
Margin="5">
DescripciĆ³n:
</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0"
Style="{StaticResource SmallTitleStyle}" Margin="0,5,0,5">
Descuento (%):
</TextBlock>
<TextBox Name="PromoEntryForm" AutomationProperties.Name="Promo Description" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FriendlyName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding Path=IsReadOnly}" />
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=IsReadOnly}" >
<TextBox.Text>
<Binding Path="DiscountPct" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</DataTemplate>
</Window.Resources>
<Border Padding="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="300"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
Style="{StaticResource TitleStyle}"
Margin="5">
Promociones:
</TextBlock>
<Border Grid.Row="2" Grid.ColumnSpan="3" Style="{StaticResource BorderStyle}">
<ListView Name="ListViewPromos" Grid.Row="1" Grid.ColumnSpan="3" ItemsSource="{Binding Source={StaticResource PromoListingDataView}}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="OnSelectionChanged"
MouseDoubleClick="EnterEditMode"
IsEnabled="{Binding Path=IsReadOnly}">
<ListView.View>
<GridView AllowsColumnReorder="true" ColumnHeaderToolTip="Info de la promociĆ³n">
<GridViewColumn DisplayMemberBinding="{Binding Path=FriendlyName}" Header="Nombre de la promo" Width="300"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=DiscountPct, StringFormat=N2}" Header="Descuento (%)" Width="150"/>
</GridView>
</ListView.View>
</ListView>
</Border>
<Grid Grid.Row="4" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentControl Name="PromoDetail" Grid.Row="0" Grid.ColumnSpan="2"
Content="{Binding Path=Current}"
ContentTemplate="{StaticResource PromoDetailTemplate}"
Margin="9,0,0,0" />
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button Name="NewPromoButton" HorizontalAlignment="Right" Content="_Nueva" Style="{StaticResource AcctionButtonStyle}"
Click="NewPromo" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="EditPromoButton" HorizontalAlignment="Right" Content="_Modificar" Style="{StaticResource AcctionButtonStyle}"
Click="EditPromo" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="SubmitPromoButton" HorizontalAlignment="Right" Content="_Aceptar" Style="{StaticResource AcctionButtonStyle}"
Click="SubmitPromo" Visibility="{Binding Path=ShowIfEditing}"/>
<Button Name="CancelEditButton" HorizontalAlignment="Right" Content="_Cancelar" Style="{StaticResource AcctionButtonStyle}"
Click="UndoEdit" Visibility="{Binding Path=ShowIfEditing}"/>
</StackPanel>
</Grid>
</Grid>
</Border>
Use Element Binding instead of relative source.
First Provide name for the Window.
<Window x:Class="CataloGo.View.PromoWindow"
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:localroot="clr-namespace:CataloGo"
xmlns:local="clr-namespace:CataloGo.View"
xmlns:localVm="clr-namespace:CataloGo.ViewModel"
xmlns:localModel="clr-namespace:CataloGo.Model"
mc:Ignorable="d"
x:Name="Window1"
d:DataContext="{d:DesignInstance localVm:PromoViewModel, d:IsDesignTimeCreatable=True}"
Title="{Binding Path=Title}" SizeToContent="WidthAndHeight" MinWidth="640" MinHeight="480">
Element Binding Code in xaml
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding ElementName=Window1, Path=DataContext.IsReadOnly}" >
<TextBox.Text>
<Binding Path="DiscountPct" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Bizarre WPF Template Rerendering

My WPF application is employing the MVVM pattern, where the views are discovered using WPFs type-based DataTemplate mechanism.
I have one View that contains a ListBox, and the DataTemplate for the ViewModel type in the ListBox is in a Resource on the Grid that contains the ListBox.
In this DataTemplate, I have a custom Button class that has a DependencyProperty that is bound to a string property on the underlying ViewModel. When the Button is clicked, code-behind in the custom Button class gets the value out of the DependencyProperty and passes it in to a modal dialog, gets the edited value back from the dialog and updates the DependencyProperty (which then flows, through binding, back into the ViewModel). This whole process works fine.
However, the very first time this happens, something in the rendering of the dialog causes WPF to do something which results in the items in the ListBox being re-rendered. As a result of this, the actual Button that was clicked is detached from the Visual Tree, and the object being bound to is "disconnected". This only happens the very first time the dialog is evoked during the run of the application. Once it happens, everything is fine until you restart the application.
I'm at a loss. My best guess (based on the call stack for the constructor of my custom Button) is that something is causing WPF to recompile the resources for my application (or, at least, this one View), but I can't imagine what, nor how to prevent it (or force it to happen during startup).
Any suggestions would be awesome. Thanks!
David Mullin
IMA Technologies
Edited: Here's an attempt to show what the code looks like (boiler plate code omitted)
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type ct:RecordDisplayDataSourceAuthoringViewModel}">
<ctConfigControls:SetInfoButton Info="{Binding AutoNewSetInfoXml.Value}" Content="Set Info" />
</DataTemplate>
</Grid.Resources>
<!-- The DataSources property contains a list of RecordDisplayDataSourceAuthoringViewModel -->
<ListBox Grid.Row="1" ItemsSource="{Binding Path=DataSources}" />
</Grid>
public class SetInfoButton : Button
{
public static readonly DependencyProperty InfoProperty = DependencyProperty.Register("Info", typeof(string), typeof(SetInfoButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Info
{
get { return (string)GetValue(InfoProperty); }
set { SetValue(InfoProperty, value); }
}
public SetInfoButton()
{
Click += new RoutedEventHandler(SetInfoButton_Click);
Focusable = false;
}
void SetInfoButton_Click(object sender, RoutedEventArgs e)
{
string info = Info;
if (SetInfoDialog.Show(VisualTreeHelperEx.FindVisualAncestor<Window>(this), ref info) == true)
{
Info = info;
}
}
}
public partial class SetInfoDialog : DialogWindow, IDesignerDataSource
{
public static bool Show(Window parent, ref string info)
{
SetInfoDialog dlg = new SetInfoDialog(info);
dlg.Owner = parent;
if (dlg.ShowDialog() == true)
{
info = dlg.RawInfo;
return true;
}
return false;
}
public SetInfoDialog(string rawInfo)
{
//...
}
}
<ct:DialogWindow x:Class="CaseTrakker.Windows.Dialogs.SetInfoDialog">
<ct:DialogWindow.Buttons>
<ct:ImageButton Image="{DynamicResource {x:Static ct:Common.OkImageKey}}" Content="_Ok" Click="Ok_Click" />
<ct:ImageButton Image="{DynamicResource {x:Static ct:Common.CancelImageKey}}" Content="_Cancel" Click="Cancel_Click" IsCancel="True" />
</ct:DialogWindow.Buttons>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Visibility="{Binding Path=HideObjectSelectorControls, Converter={StaticResource InvertedBoolToVisibilityConverter}}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">System Object</Label>
<ct:SystemObjectComboBox Grid.Row="0" Grid.Column="1" CreateOnly="True" SelectedValue="{Binding Path=SystemObjectNumber}" />
<Label Grid.Row="1" Grid.Column="0">Type</Label>
<ctConfigControls:TemplateComboBox Grid.Row="1" Grid.Column="1"
SystemObject="{Binding Path=SystemObjectNumber, Converter={StaticResource IntToStandardSystemObjectConverter}}"
SelectedValue="{Binding Path=TemplateId}" />
</Grid>
<Button Grid.Row="0" Grid.Column="1" Click="Import_Click">Import</Button>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Vertical" Margin="4,8,4,4"
Visibility="{Binding Path=DisplayText, Converter={StaticResource EmptyToVisibilityConverter}}">
<Label FontWeight="Bold">Notes</Label>
<TextBox Text="{Binding Path=DisplayText, Mode=OneWay}" IsReadOnly="True"/>
</StackPanel>
<ctConfigDesigner:DataDesigner x:Name="Designer" Grid.Column="1" Loaded="DataDesigner_Loaded" />
</Grid>
</Grid>
</ct:DialogWindow>
<Style x:Key="DialogWindowStyle" TargetType="{x:Type ctWindows:DialogWindow}" BasedOn="{StaticResource {x:Type Window}}">
<Setter Property="Background" Value="{DynamicResource {x:Static ctThemes:Common.StandardWindowBackgroundBrushKey}}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.LayoutTransform>
<ScaleTransform x:Name="ZoomFrame"
ScaleX="{Binding Path=ZoomValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
ScaleY="{Binding Path=ZoomValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"/>
</Grid.LayoutTransform>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Height="35" Width="35"
Visibility="{Binding Path=HasTitleImage, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}, Converter={StaticResource BoolToVisibilityConverter}}"
Source="{Binding Path=TitleImage, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Path=Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Style="{DynamicResource LabelTitle}" FontWeight="Bold" />
<ContentControl Grid.Row="0" Grid.Column="2"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Content="{Binding Path=HeaderContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
</Grid>
<ContentControl Grid.Row="1" Grid.Column="0"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Content="{Binding Path=WindowContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
<ContentControl Grid.Row="2" Grid.Column="0"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}"
Content="{Binding Path=ButtonPanel, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctWindows:DialogWindow}}}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>

Sharing control instance within a view in WPF

I'm having some issues with the wpf tab control. Not sure the title of the question is right I will refine it accoring to answers.
I want to create a simple panel system. I want to inject ot my "panel viewModel" 2 view model
MainViewModel will be display as the main area
PanelViewModel will be display as a panel on the right hand side of the view
the panelViewModel will be hidden by default and a button will display it on top of the main view model when needed
The view look like this:
<UserControl.Resources>
<DataTemplate x:Key="MainWindowTemplate" DataType="{x:Type UserControl}">
<ContentPresenter Content="{Binding DataContext.MainViewModel, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" ContentTemplate="{StaticResource MainWindowTemplate}" />
<Button Grid.Column="1" Content="{Binding PanelTitle}" Command="{Binding Path=ShowPanelCommand}">
<Button.LayoutTransform>
<RotateTransform Angle="90"/>
</Button.LayoutTransform>
</Button>
</Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl ContentTemplate="{StaticResource MainWindowTemplate}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="2" VerticalAlignment="Stretch" Background="Red" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border HorizontalAlignment="Stretch">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding PanelTitle}" Margin="5,5,0,2" HorizontalAlignment="Left"></TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0,0,5,0" HorizontalAlignment="Right" >
<Button Content="Minimze" Command="{Binding HidePanelCommand}"/>
</StackPanel>
</Grid>
</Border>
<ContentPresenter Grid.Row="1" Margin="2" Content="{Binding PanelViewModel}" VerticalAlignment="Top"/>
</Grid>
</Border>
</Grid>
</Grid>
The view model look like that:
public class TestTabViewModel : ObservableObject
{
#region private attributes
#endregion
public TestTabViewModel(string panelName, object panelViewModel, object mainViewModel)
{
IsPanelHidden = true;
PanelTitle = panelName;
PanelViewModel = panelViewModel;
MainViewModel = mainViewModel;
ShowPanelCommand = new DelegateCommand(() =>ManagePanelVisibility(true));
HidePanelCommand = new DelegateCommand(() => ManagePanelVisibility(false));
}
#region properties
public string PanelTitle { get; private set; }
public bool IsPanelHidden { get; private set; }
public object PanelViewModel { get; private set; }
public object MainViewModel { get; private set; }
public DelegateCommand ShowPanelCommand { get; private set; }
public DelegateCommand HidePanelCommand { get; private set; }
#endregion
#region private methods
private void ManagePanelVisibility(bool visible)
{
IsPanelHidden = !visible;
RaisePropertyChanged(() => IsPanelHidden);
}
#endregion
}
So for so good, this system work fine I aslo added some pin command but I remove them from here to make it "simple".
My problem come when the main view model hold a tab control. In this, case if I select a tab and "open" the panel, the tab selected is "changed". In fact it's not changed it's just that I display another contentControl which is not synchronize with the previouse one. I guess that the view instance is not the same even if the viewmodel behind is.
So how do I share a view instance within a view (or have the selection process synchornized)? My first guest was to use the datatemplate (as show in the example) but it did not solve my problem.
By the way, I know some third-party handling panel docking pin ... (eg avalon) but all the one I found are really too much for my simple need.
Thanks for the help
Your best bet would probably be to replace your two Grids with a single ContentControl, and switch the Template on button click or in a Trigger. This way your actual Content (the TabControl) will be the same, but the template used to display the Content will change
Here's a quick example:
<Window.Resources>
<ControlTemplate x:Key="Grid1Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Left" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
<ControlTemplate x:Key="Grid2Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Right" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="btnToggle" Content="Toggle View" DockPanel.Dock="Top" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template" Value="{StaticResource Grid1Template}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=btnToggle, Path=IsChecked}" Value="True">
<Setter Property="Template" Value="{StaticResource Grid2Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<TabControl>
<TabItem Header="Tab1" />
<TabItem Header="Tab2" />
<TabItem Header="Tab3" />
</TabControl>
</ContentControl>
</DockPanel>
i dont know if i get what you want but i think that you could do the following.
i assume that you want some "MainViewmodeldata" be presented as your tabcontrol.
so i woulf first create a datatemplate for this.
<UserControl.Resources>
<DataTemplate DataType="{x:Type MainViewmodeldata}">
<TabControl>
<TabItem Header="Tab1">
<TextBlock Grid.Column="1" Text="Tab1Content"/>
</TabItem>
<TabItem Header="Tab2">
<TextBlock Grid.Column="1" Text="Tab2Content"/>
</TabItem>
</TabControl>
</DataTemplate>
</UserControl.Resources>
now i would just bind my this mainviewmodeldata to the contentcontrol and let wpf render it for you. i really dont know if you still need these two grids, cause i dont know what you wanna achieve.
<Grid x:Name="Grid1" Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel Grid.Column="1">
<Button x:Name="Button1" Content="Switch look" Command="{Binding ShowPanelCommand}"/>
<TextBlock Text="Look1"/>
</StackPanel>
</Grid>
<Grid x:Name="Grid2" Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel HorizontalAlignment="Right">
<Button x:Name="Button2" Content="Switch look" Command="{Binding HidePanelCommand}"/>
<TextBlock Text="Look2"/>
</StackPanel>
</Grid>
</Grid>

MVVM + UserControl + UserControl + DependencyProperty

I have a user control which displays addresses housed inside of another page that is bound to a viewmodel. The viewmodel has a primitive User which has a collection of Address objects. The User control will reside on several pages so I would like to be able to bind it to the address list via a dependency property. While my current solution is working, something about it just doesn't feel right and I thought I'd ask for a second opinion. I have chopped out a lot of code for brevity's sake.
Basically the page binds to the dependency property in the usercontrols code behind which then updates the usercontrol's datagrid by setting it's itemsource. This seems to me to break the basic tenants of MVVM.
AddressListView control:
<UserControl x:Class="Insight.Controls.AddressListView"
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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:tk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:command="clr-namespace:PrismFramework.Implementors.Commanding;assembly=PrismFramework"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="840">
<UserControl.Resources>
<command:ObservableCommand x:Name="EditAddressCommand" Value="{Binding EditAddressCmd}"/>
<command:ObservableCommand x:Name="DeleteAddressCommand" Value="{Binding DeleteAddressCmd}"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid Name="dgAddresses"
Height="Auto"
Width="Auto"
AutoGenerateColumns="False"
HeadersVisibility="None" >
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn x:Name="dgcAddresses"
Width="*" >
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border x:Name="bdrAddress"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
BorderBrush="Silver"
BorderThickness="1"
Padding="0"
Margin="1,1,1,1">
<Grid x:Name="grdAddressItem"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="55" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Padding="0,0,5,0" Text="Type:" TextAlignment="Right" />
<TextBlock Grid.Column="1" Padding="0" Text="{Binding Path=AType}" Grid.ColumnSpan="2" />
<TextBlock Grid.Row ="1" Grid.Column="0" Padding="0,0,5,0" Text="Address 1:" TextAlignment="Right" />
<!-- List Of Similar Fields ->
<Grid x:Name="grdAddressEditOptions"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
Grid.Column="3"
Grid.RowSpan="7" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button x:Name="btnEdit"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
Grid.Row="0"
Padding="4,5,4,8"
Margin="0,8,10,0"
Command="{Binding Value, Source={StaticResource EditAddressCommand}}"
CommandParameter="{Binding}" >
<Button.Content>
<Image x:Name="btnEditIcon"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="Auto"
Width="Auto"
Source="/Insight.ModuleUser;component/Images/edit.png"
Visibility="Visible" />
</Button.Content>
</Button>
<Button x:Name="btnDelete"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
Grid.Row="2"
Padding="4,5,4,8"
Margin="0,0,10,5"
Command="{Binding Value, Source={StaticResource DeleteAddressCommand}}"
CommandParameter="{Binding}" >
<Button.Content>
<Image x:Name="btnDeleteIcon"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="Auto"
Width="Auto"
Source="/Insight.ModuleUser;component/Images/delete.png"
Visibility="Visible" />
</Button.Content>
</Button>
</Grid>
</Grid>
</Border>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
AddressListView code behind:
Imports System.Collections.ObjectModel
Imports Insight.DataServices.Primitives
Partial Public Class AddressListView
Inherits UserControl
Public ReadOnly AddressesProperty As DependencyProperty = DependencyProperty.Register("Addresses", GetType(ObservableCollection(Of Address)), GetType(AddressListView), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnAddressesChanged)))
Public Sub New()
InitializeComponent()
End Sub
Public Property Addresses As ObservableCollection(Of Address)
Get
Return DirectCast(GetValue(AddressesProperty), ObservableCollection(Of Address))
End Get
Set(value As ObservableCollection(Of Address))
SetValue(AddressesProperty, value)
End Set
End Property
Public Sub OnAddressesChanged()
Me.dgAddresses.ItemsSource = Addresses
End Sub
End Class
Base page:
<UserControl x:Class="Insight.ModuleUser.Views.EditUserView"
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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cm="clr-namespace:System.ComponentModel;assembly=System.Windows"
xmlns:data="clr-namespace:System.Windows.Data;assembly=System.Windows"
xmlns:vm="clr-namespace:Insight.ModuleUser.ViewModels"
xmlns:command="clr-namespace:PrismFramework.Implementors.Commanding;assembly=PrismFramework"
xmlns:controls="clr-namespace:Insight.Controls;assembly=Insight.Controls"
xmlns:modalDialogs="clr-namespace:Insight.Controls.ModalDialogViews;assembly=Insight.Controls"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="1144"
d:DataContext="{d:DesignData /Insight.ModuleUser;component/SampleData/EditUserViewModelSampleData.xaml}">
<UserControl.Resources>
<command:ObservableCommand x:Name="OpenProjectCommand" Value="{Binding OpenProjectCmd}"/>
<command:ObservableCommand x:Name="OpenPaymentCommand" Value="{Binding OpenPaymentCmd}"/>
<command:ObservableCommand x:Name="OpenInvoiceCommand" Value="{Binding OpenInvoiceCmd}"/>
<command:ObservableCommand x:Name="OpenPaymentItemCommand" Value="{Binding OpenPaymentItemCmd}"/>
<command:ObservableCommand x:Name="EditPhoneCommand" Value="{Binding EditPhoneNumberCmd}"/>
<command:ObservableCommand x:Name="DeletePhoneCommand" Value="{Binding DeletePhoneNumberCmd}"/>
<command:ObservableCommand x:Name="EditEmailAddressCommand" Value="{Binding EditEmailAddressCmd}"/>
<command:ObservableCommand x:Name="DeleteEmailAddressCommand" Value="{Binding DeleteEmailAddressCmd}"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" >
<controls:AddressListView x:Name="ctrlAddressListView"
Addresses="{Binding User.Addresses}" />
</Grid>
</UserControl>
This seems a perfectly reasonable approach. However, you could use binding in your user control view, rather than setting the items source in code.
To do this you need to set the DataContext of the user control to be your user control type. This could be done either in the code behind for the user control (setting this.DataContext = this), or through element binding in XAML:
<UserControl
...
x:Name="MyName"
DataContext="{Binding ElementName=MyName}"
However, my approach would be not to use a user control at all, as all you're really talking about is view composition and reusing a particular section of the view between other views.
View composition is extremely straightforward with an MVVM framework such as Caliburn.Micro. In this case you would have an AddressViewModel and AddressView, and use a ContentControl to inject the AddressView into the base view:
<ContentControl x:Name="AddressViewModel" />

WPF items not visible when grouping is applied

I'm having this strange issue with my ItemsControl grouping. I have the following setup:
<ItemsControl Margin="3" ItemsSource="{Binding Communications.View}" >
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemCount, StringFormat='{}[{0}] '}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Text="{Binding Name, Converter={StaticResource GroupingFormatter}, StringFormat='{}Subject: {0}'}" FontWeight="Bold" />
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock FontWeight="Bold" Text="{Binding Inspector, Converter={StaticResource NameFormatter}, StringFormat='{}From {0}:'}" Margin="3" />
<TextBlock Text="{Binding SentDate, StringFormat='{}{0:dd/MM/yy}'}" Grid.Row="1" Margin="3"/>
<TextBlock Text="{Binding Message }" Grid.Column="1" Grid.RowSpan="2" Margin="3"/>
<Button Command="vm:CommunicationViewModel.DeleteMessageCommand" CommandParameter="{Binding}" HorizontalAlignment="Right" Grid.Column="2">Delete</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my ViewModel, I expose a CollectionViewSource named 'Communications'. I proceed to adding a grouping patter like so:
Communications.GroupDescriptions.Add(new PropertyGroupDescription("Subject"));
Now, the problem i'm experience is the grouping work fine, but I can't see any items inside the groups. What am I doing wrong? Any pointers would be much appreciated.
I can't seem to reproduce the problem - I assume you are using a CollectionViewSource? It might be because you bound to the View property directly.
Here's the C# code I used:
public class Communication
{
public string Subject { get; set; }
public string Body { get; set; }
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var source = (CollectionViewSource)Resources["Communications"];
source.Source = new List<Communication>()
{
new Communication { Subject = "WPF 4.0", Body = "I love what's happening with 4.0"},
new Communication { Subject = "WPF 4.0", Body = "I hear the text rendering is the best feature"},
new Communication { Subject = "Blend 3.0", Body = "Behaviors in Blend 3 change everything"}
};
source.GroupDescriptions.Add(new PropertyGroupDescription("Subject"));
}
}
Here is the XAML - it's the same as yours but with a couple of things removed since I don't have your converters or commands:
<Window
x:Class="GroupStyleDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<Window.Resources>
<CollectionViewSource x:Key="Communications" />
</Window.Resources>
<Grid>
<ItemsControl Margin="3" ItemsSource="{Binding Source={StaticResource Communications}}" >
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemCount, StringFormat='{}[{0}] '}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Text="{Binding Path=Name}" FontWeight="Bold" />
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Body }" Grid.Column="1" Grid.RowSpan="2" Margin="3"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
After hitting this problem myself I discovered the cause: the template for ItemsControl directly including a panel with IsItemsHost="true".
You must insert an ItemPresenter into your template and set the ItemsControl.ItemsPanel property instead.

Resources