I have two comboboxes that I would to show besides each other.
I am using a grid, and two columns for this... but when I do this, the initialliy selected item for the comboxbox disappears
so, if I put them in a grid... i get this:
If I remove the grid... the combobox gets the initial value...
the xaml looks like this... here with the grid part commented out... I just don't get why adding/removing the grid makes a difference...
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="{x:Bind ViewModel.LoadErrorMessage, Mode=OneWay}" />
<!--<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>-->
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{x:Bind ViewModel.WeaponCountRange}" SelectedItem="{x:Bind ViewModel.WeaponCount, Mode=TwoWay}"></ComboBox>
<ComboBox Grid.Column="1" HorizontalAlignment="Stretch" ItemsSource="{x:Bind ViewModel.Weapons}" SelectedItem="{x:Bind ViewModel.SelectedWeapon, Mode=TwoWay}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<Border Background="Black">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageFile}" Stretch="Uniform" Height="48"></Image>
<TextBlock Foreground="Yellow" Height="48" VerticalAlignment="Stretch" Text="{Binding Name}"></TextBlock>
<Image VerticalAlignment="Top" Visibility="{Binding ShieldPiercingVis}" Height="12" Source="/Assets/ship_modules/dragon_missile.png"/>
</StackPanel>
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!--</Grid>-->
<Border>
the code behind that populates, is a async task... see the following
public ObservableCollection<WeaponViewModel> Weapons = new ObservableCollection<WeaponViewModel>();
private WeaponViewModel _selectedWeapon;
public WeaponViewModel SelectedWeapon
{
get => _selectedWeapon;
set => SetProperty(ref _selectedWeapon, value);
}
private async Task Initialize()
{
{
var wRepo = new WeaponRepository();
await wRepo.Initialize();
foreach (var item in wRepo.Weapons)
{
Weapons.Add(new WeaponViewModel(item));
if (Weapons.Count == 1)
SelectedWeapon = Weapons[0];
}
}
...
I could not reproduce your issue. When I run your code, it throw exception. And I found that you have not set IValueConverter for SelectedItem. I have created the converter for SelectedItem and it works well in my side. I will upload the code sample that you could refer to.
Converter
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value as ComboBoxItem;
}
}
Related
I'm trying to leverage a custom control within a CollectionView and would like to pass the entire object of the particular CollectionView ItemTemplate into my custom control.
Here's my xaml page:
<CollectionView ItemsSource="{Binding WorkOps}" SelectionMode="None" ItemsLayout="VerticalList">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0"
Text="{Binding OpType}"
FontSize="Caption"
VerticalTextAlignment="Center"/>
<Label Grid.Column="1"
Text="{Binding OpNumber}"
FontSize="Caption"
VerticalTextAlignment="Center"/>
<Label Grid.Column="2"
Text="{Binding Instructions}"
FontSize="Body"/>
<Entry Grid.Column="2"
Grid.Row="1"
Text="{Binding Measure}"
IsVisible="{Binding IsSimpleMeasure}" />
<root:TableMeasureView Grid.Column="2"
Grid.Row="1"
Op="{Binding .}"
IsVisible="{Binding IsTableMeasure}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
and here is my custom control I'm trying to implement:
public class TableMeasureView : Grid
{
public static readonly BindableProperty WorkOpProperty =
BindableProperty.Create(nameof(Op), typeof(WorkOp), typeof(ContentPage));
public WorkOp Op
{
get { return (WorkOp)GetValue(WorkOpProperty); }
set { SetValue(WorkOpProperty, value); }
}
public TableMeasureView()
{
}
// ...
}
I get the following message when trying to build:
XamlC error XFC0009: No property, BindableProperty, or event found for "Op", or mismatching type between value and property.
Is what I'm attempting possible?
Yes it is possible. What's happening is that the xaml doesn't attempt to figure out that the type of {Binding .} is WorkOp. It wants a property of type object.
The fix is to give it a property of type object. Then for convenient access in your custom control, make a second property that casts that to WorkOp:
public static readonly BindableProperty WorkOpProperty =
BindableProperty.Create(nameof(Op), typeof(object), typeof(ContentPage));
public object Op
{
get { return GetValue(WorkOpProperty); }
set { SetValue(WorkOpProperty, value); }
}
private WorkOp TypedOp => (WorkOp)Op;
NOTE: Change Op and TypedOp above to whatever names you like. If you change Op, remember to also change in the xaml that refers to it.
I'm in the process of converting a fairly large WPF app from three-layer into MVVM, and in the process learning MVVM. So far, I haven't delved into too much detail around Bindings (etc), so bear with me.
I'm trying to bind the System.Windows.Visibility of multiple controls to a public property ("State") of the ViewModel. When the parent TabItem loads, the State property is read and handled as desired. When subsequent changes to the property are made, however, they appear to be ignored. I've (re-re-re-)checked Bindings, debugged the Converters, etc, and this is driving me crazy.
ViewModel:
public class MarketingListViewModel: IDisposable, INotifyPropertyChanged
{
private UiState state;
public event PropertyChangedEventHandler PropertyChanged;
public UiState State
{
get { return state; }
set
{
if (state != value)
{
state = value;
NotifyPropertyChanged("State");
}
}
}
public MarketingListViewModel()
{
State = UiState.View;
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
View:
<UserControl 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:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d" x:Class="WpfCrm.tabListManager" xmlns:DPH="clr-namespace:DPH" >
<UserControl.Resources>
<DPH:MarketingListViewModel x:Key="listVM" />
<!-- Note that the above line is giving me an "Object reference not set to an instance of an object" error -->
</UserControl.Resources>
<Grid x:Name="gridMain" DataContext="{StaticResource listVM}" >
<Border Grid.Row="0" Grid.Column="1" Margin="10"
Style="{StaticResource WidgetStyle}" >
<Grid x:Name="gridListManagement" >
<Label x:Name="labelManageLists" Content="Manage Lists" MouseDown="labelManageLists_MouseDown"
Style="{StaticResource WidgetTitleStyle}"
Grid.Row="0" Grid.Column="0" />
<StackPanel Grid.Row="0" Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" >
<Label x:Name="llNewList" Content="new" MouseDown="llNewList_MouseDown"
Style="{StaticResource LinkLabelStyle}"
HorizontalAlignment="Right" />
<Label x:Name="llCloseManageLists" Content="close" MouseDown="llCloseManageLists_MouseDown"
Style="{StaticResource LinkLabelStyle}"
HorizontalAlignment="Right" />
</StackPanel>
<Label x:Name="labelListName" Content="Name" Grid.Row="1" Grid.Column="0" />
<Grid Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" >
<ComboBox x:Name="cbLists" SelectedIndex="-1" SelectionChanged="cbLists_SelectionChanged" IsReadOnly="True"
ItemsSource="{Binding Path=AllMarketingLists}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
Visibility="{Binding Path=State, Converter={StaticResource ViewStateToVisibilityConverter} }"/>
<TextBox x:Name="tbListName"
Text="{Binding Path=OList.Name}"
Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"/>
</Grid>
<Label x:Name="labelListDescription" Content="Description" Grid.Row="2" Grid.Column="0" />
<Grid Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" >
<TextBlock x:Name="textblockListDescription" TextWrapping="Wrap"
Text="{Binding Path=OList.Notes}"
Visibility="{Binding Path=State, Converter={StaticResource ViewStateToVisibilityConverter} }"
Grid.ColumnSpan="2" />
<TextBox x:Name="tbListDescription" TextWrapping="Wrap"
Text="{Binding Path=OList.Notes}"
Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"
Grid.ColumnSpan="2" />
</Grid>
<StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Right" Orientation="Horizontal" >
<Button x:Name="buttonEditList" Content="Edit" Click="buttonEditList_Click"
Visibility="{Binding Path=State, Converter={StaticResource ViewStateToVisibilityConverter} }"
Width="60" Margin="3" />
<Button x:Name="buttonSaveList" Content="Save" Click="buttonSaveList_Click"
Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"
Width="60" Margin="3" />
<Button x:Name="buttonCancel" Content="Cancel" Click="buttonCancel_Click"
Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"
Width="60" Margin="3" />
</StackPanel>
</Grid>
</Border>
</Grid>
And the code-behind has a few methods like:
private void buttonEditList_Click(object sender, RoutedEventArgs e)
{
listVM.State = UiState.Edit;
}
Does anyone have ideas on why the controls aren't updating their visibility after the State change?
Kind thanks,
DPH
EDIT -- Converters:
[ValueConversion(typeof(WpfCrm.UiState), typeof(System.Windows.Visibility))]
public class EditStateToVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
UiState state = (UiState)value;
if (state == UiState.View) return Visibility.Collapsed;
else return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
[ValueConversion(typeof(WpfCrm.UiState), typeof(System.Windows.Visibility))]
public class ViewStateToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
UiState state = (UiState)value;
if (state == UiState.View) return Visibility.Visible;
else return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
You seem to be used two different instances of your view model, one declared and used in XAML
<UserControl.Resources>
<DPH:MarketingListViewModel x:Key="listVM" />
</UserControl.Resources>
<Grid DataContext="{StaticResource listVM}" >
and one in code-behind (from comment):
listVM = new MarketingListViewModel();
You should of course be using only one. So change your code behind declaration to
listVM = (MarketingListViewModel)Resources["listVM"];
OK, there is a mistake. You declare listVM as
var listVM = new MarketingListViewModel();
But this is not the listVM from your XAML. In XAML you have created another instance of MarketingListViewModel. So, when you try to change listVM that was declared in code, nothing happens because this object is not DataContext of your Grid.
In your Click handler you have to write the following:
private void buttonEditList_Click(object sender, RoutedEventArgs e)
{
var _listVM = (MarketingListViewModel)FindResource("listVM");
_listVM.State = UiState.Edit;
}
OR replace your listVM declaration in code-behind with this one:
listVM = (MarketingListViewModel)FindResource("listVM");
Then you won't need to change event handlers.
Hope, it helps.
I am trying to bind a dictionary to two textblocks in a listview. The listview ItemsSource binding is defined in the code behind and the text blocks content is in the XAML.
I am able to display the items but they are displayed with square brackets around each row like [stringA, stringB]. However, this format will not work. The latest code that I tried was by setting the Key and Value which did not work was:
XAML:
<ListView Name="lvListLogs"
Margin="0,10,0,0">
<ListView.ItemTemplate>
<DataTemplate x:Name="ListItemTemplate">
<Grid Margin="5,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="122"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="104"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="tb_PointName" Grid.Column="1"
Text="{Binding Key}"
Margin="10,0,0,0" FontSize="40"
TextWrapping="Wrap"
MaxHeight="72"
Foreground="#FFFE5815" />
<TextBlock x:Name="tb_PointValue" Grid.Column="1"
Text="{Binding Value}"
Margin="10,0,0,0" FontSize="40"
TextWrapping="Wrap"
MaxHeight="72"
Foreground="#FFFE5815" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C# (abridged for clarity):
public Dictionary<string, string> mydict2 { get; set; }
mydict2 = new Dictionary<string, string>();
if (item != null)
{
var props = item.GetType().GetRuntimeProperties();
foreach (var prop in props)
{
foreach (var itm in group1.Items.Where(x => x.UniqueId == prop.Name))
{
var _Title = prop.Name;
var _Value = prop.GetValue(item, null);
string propertyValue;
string propertyName;
propertyValue = Convert.ToString(_Value);
propertyName = _Title;
mydict2.Add(_Title, propertyValue);
}
}
//binding here
lvListLogs.ItemsSource = mydict2;
}
Any assistance would be appreciated.
Your code works fine, the problem is you set the same Grid.Column for both TextBlocks. The first column index should be zero:
<TextBlock x:Name="tb_PointName" Grid.Column="0" ...
To achieve the required binding, instead of the Dictionary I used an ObservableCollection with the class and constructor.
To databind the listview (xaml) to ObservableCollection:
Create the Class with Constructor
public class PointInfoClass
{
public string PointName { get; set; }
public string PointValue { get; set; }
public PointInfoClass(string pointname, string pointvalue)
{
PointName = pointname;
PointValue = pointvalue;
}
}
Create collection of the PointInfoClass
public ObservableCollection<PointInfoClass> PointInfo
{
get
{
return returnPointInfo;
}
}
Instantiate the collection
ObservableCollection<PointInfoClass> returnPointInfo = new ObservableCollection<PointInfoClass>();
Add item to collection
returnPointInfo.Add(new PointInfoClass(string1, string2));
Databind to the ObservableCollection name.
The xaml code:
<ListView
Grid.Row="1"
ItemsSource="{Binding PointInfo}"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick"
Margin="19,0.5,22,-0.333"
x:Name="lvPointInfo"
Background="White">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Margin="0,0,0,20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="270"/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="1" VerticalAlignment="Top">
<TextBlock x:Name="tb_PointSubTitle" Grid.Column="1"
Text="{Binding PointName}"
Margin="10,0,0,0" FontSize="20"
TextWrapping="Wrap"
MaxHeight="72"
Foreground="#FF5B5B5B"
/>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Column="2" VerticalAlignment="Top" HorizontalAlignment="Right">
<TextBlock x:Name="tb_PointValue"
Grid.Column="1"
Text="{Binding PointValue}"
Margin="0,5,0,0" FontSize="20"
HorizontalAlignment="Right"
TextWrapping="Wrap"
FontWeight="Normal"
Foreground="Black" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Set the DataContext of the ListView
lvPointInfo.DataContext = this;
This code is edited for clarity.
I have a Grid control that is proportioned using star e.g.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
However putting a long TextBlock in the grid that overflows causes the proportions to be upset. e.g.
<TextBlock Text="Foo" Grid.Column="0" />
<TextBlock Text="Some long text here which overflows" Grid.Column="1" />
<TextBlock Text="Foo" Grid.Column="2" />
This causes the central column to be more than double the other two. How do I maintain the specified proportions? Is it possible to clip the content?
I have set TextTrimming="CharacterEllipsis" on the TextBlocks but no luck.
Edit
Crucially it seems, the Grid is inside a DataTemplate, paste the following to observe the behaviour,
<!-- FallbackValue is just a quick hack to get some rows to show at design-time -->
<ListBox ItemsSource="{Binding Foo, FallbackValue=1234}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Foo" Grid.Column="0" />
<TextBlock Text="Some long text here which overflows" TextTrimming="CharacterEllipsis" Grid.Column="1" />
<TextBlock Text="Foo" Grid.Column="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The reason why this is important is that I have another Grid as a sibling of the ListBox which displays the 'headers' for the columns shown in the ListBox as follows,
<Grid>
... Headers and column definitions here
</Grid>
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
... Matching column definitions here
</Grid>
</DateTemplate>
</ListBox.ItemTemplate>
</ListBox>
and so it is important that the columns match up.
I have tried to bind the ColumnDefinitions inside the DataTemplate to the external Grid ColumnDefinitions but I cannot get easily a binding reference to it.
This is one of the most annoying problems with WPF. Since the available space yielded to the templated grid is infinite, the actual content will take as much space as it wants.
The simplest way is to fix a certain width to the Grid, but that solves only the situations where there's no resizing.
Whereas you want to stretch the ListBox size (width, in the specific), unfortunately I guess that there's no any better solution other than a custom converter.
Here is my solution:
<Window.Resources>
<local:MyConv x:Key="cv1" />
</Window.Resources>
<Grid>
<ListBox
ItemsSource="{Binding Foo, FallbackValue=1234}"
HorizontalContentAlignment="Stretch"
>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Converter={StaticResource cv1}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Foo" Grid.Column="0" />
<TextBlock Text="Some long text here which overflows" TextTrimming="CharacterEllipsis" Grid.Column="1" />
<TextBlock Text="Foo" Grid.Column="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And the converter:
class MyConv : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture
)
{
return (double)value - 30.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Even though this is an old post I'm adding my findings as they might be relevant for other people reading this post.
I had a similar issue (my * columns weren't dividing the width evenly as expected anymore, they were just sizing based on the content).
The root cause here was that I had a ListView with an ItemsSource linked to a List. The ListView in WPF contains a ScrollViewer and a ScrollViewer doesn't have a fixed width.
Without a fixed width a Grid can't properly determine what width to give to a * column and switches to a different sizing method.
Solution
I now use an ItemsControl which doesn't contain a ScrollViewer and thus the Width is known allowing the Grid to properly size it's columns.
For more details on how exactly the Grid handles it's sizing I suggest you decompile the Grid class and have a look at the following method:
protected override Size MeasureOverride(Size constraint)
This is my MainWindow.xaml from my test application (comment out the ListView to see the difference in behaviour):
<Window x:Class="WPFSO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfso="clr-namespace:WPFSO"
Title="MainWindow" Height="150" Width="525">
<Window.DataContext>
<wpfso:SharedSizeScopeViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type wpfso:TestViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" x:Name="SecondColumn" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" x:Name="FourthColumn" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}" />
<TextBlock Grid.Column="1" Background="LightGray" Text="{Binding Name2}"/>
<TextBlock Grid.Column="2" Text="{Binding Name3}"/>
<TextBlock Grid.Column="3" Background="Orange" Text="{Binding Name4}"/>
<!--<TextBlock Grid.Column="1" Background="Blue" HorizontalAlignment="Stretch" />
<TextBlock Grid.Column="3" Background="Orange" HorizontalAlignment="Stretch" />-->
</Grid>
</DataTemplate>
<DataTemplate x:Key="MainDataTemplate" DataType="wpfso:SharedSizeScopeViewModel" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<CheckBox Grid.Row="0" Grid.ColumnSpan="4" HorizontalAlignment="Left" FlowDirection="RightToLeft" Margin="0,0,0,25">
<TextBlock FlowDirection="LeftToRight" Text="Show differences" Style="{StaticResource LabelStyle}" />
</CheckBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="PropertyName" Style="{StaticResource LabelStyle}" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Previous value" Style="{StaticResource LabelStyle}" />
<TextBlock Grid.Row="1" Grid.Column="3" Text="Current value" Style="{StaticResource LabelStyle}" />
<ListView Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" ItemsSource="{Binding Entries}" HorizontalAlignment="Stretch" Margin="0" HorizontalContentAlignment="Stretch"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Name="RootGrid">
<ItemsControl ItemsSource="{Binding Entries}" />
<!--<ListView ItemsSource="{Binding Entries}" />-->
</Grid>
</Window>
The ViewModels used during this test:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPFSO
{
public class SharedSizeScopeViewModel : INotifyPropertyChanged
{
public SharedSizeScopeViewModel()
{
var testEntries = new ObservableCollection<TestViewModel>();
testEntries.Add(new TestViewModel
{
Name = "Test",
Name2 = "Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test",
Name3 = "Short test",
Name4 = "Nothing"
});
Entries = testEntries;
}
private ObservableCollection<TestViewModel> _entries;
public ObservableCollection<TestViewModel> Entries
{
get { return _entries; }
set
{
_entries = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
First viewmodel
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPFSO
{
public class SharedSizeScopeViewModel : INotifyPropertyChanged
{
public SharedSizeScopeViewModel()
{
var testEntries = new ObservableCollection<TestViewModel>();
testEntries.Add(new TestViewModel
{
Name = "Test",
Name2 = "Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test",
Name3 = "Short test",
Name4 = "Nothing"
});
Entries = testEntries;
}
private ObservableCollection<TestViewModel> _entries;
public ObservableCollection<TestViewModel> Entries
{
get { return _entries; }
set
{
_entries = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Second viewmodel
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPFSO
{
public class TestViewModel : INotifyPropertyChanged
{
private string _name;
private string _name2;
private string _name3;
private string _name4;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public string Name2
{
get { return _name2; }
set
{
_name2 = value;
OnPropertyChanged();
}
}
public string Name3
{
get { return _name3; }
set
{
_name3 = value;
OnPropertyChanged();
}
}
public string Name4
{
get { return _name4; }
set
{
_name4 = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Set
TextTrimming="CharacterEllipsis"
on the TextBlock.
It works for me. As you have defined the middle column should be twice the size of the other.
I find myself in a similar situation but TextTrimming isn't available.
Ends up binding child Width to Grid.ActualWidth with a Converter converts the ratio into absolute width.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<local:PartConv x:Key="partConv"/>
<sys:Double x:Key="r0">0.25</sys:Double>
<sys:Double x:Key="r1">0.5</sys:Double>
<sys:Double x:Key="r2">0.25</sys:Double>
</Grid.Resources>
<TextBlock Text="Foo" Grid.Column="0"
Width="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=Grid}},
Converter={StaticResource partConv},
ConverterParameter={StaticResource r0}}"/>
<TextBlock Text="Some long text here which overflows" Grid.Column="1"
Width="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=Grid}},
Converter={StaticResource partConv},
ConverterParameter={StaticResource r1}}"/>
<TextBlock Text="Foo" Grid.Column="2"
Width="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=Grid}},
Converter={StaticResource partConv},
ConverterParameter={StaticResource r2}}"/>
</Grid>
[ValueConversion(typeof(double), typeof(double))]
public class PartConv : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> ((double)value) * ((double)parameter);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> ((double)value) / ((double)parameter);
}
I have a pretty complex question:
At my work, we use WPF with MVVM standards.
On one of our UserControls, there is a treeview that loads all the tables from the database. For each table, the name is loaded in the list by the treeview. When you click on the table name, you can add data to that table from the screen.
Basically, it loads the column names from the table on the side, and lets you enter data and save. The records are then saved and added as children to the table. From here, selecting a child lets you update that info.
Now, the current app loads the data fine. The table names and data are loaded. The child data loads.
Everything is fine, but when it comes to loading the controls, we only use Textboxes.
On the usercontrol the code is:
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="AdditionalItemsTemplate">
<Border>
<StackPanel>
<Label
Content="{Binding Name}"
Style="{StaticResource PanelLabelStyle}"/>
<TextBox
Text="{Binding Value}"
Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
</Border>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Border
Height="350"
Width="{Binding Width}"
Style="{StaticResource InnerMenuBorderStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border
Grid.Row="0"
Grid.Column="0"
Width="230"
IsEnabled="{Binding IsUpdateEnabled}">
<StackPanel>
<Border
Margin="3,3,3,0"
Style="{StaticResource PanelBorderStyle}">
<StackPanel>
<Label Style="{StaticResource PanelLabelStyle}">
<TextBlock
Text="{Binding TableName}"
TextWrapping="WrapWithOverflow"/>
</Label>
<TextBox
Style="{StaticResource TextBoxStyle}"
Text="{Binding Value}"
TextWrapping="Wrap"/>
<ItemsControl
Width="222"
HorizontalAlignment="Left"
ItemsSource="{Binding additionalFields}" Margin="0,0,-226,0"
ItemTemplate="{StaticResource AdditionalItemsTemplate}"/>
</StackPanel>
</Border>
<Button
Width="70"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="3.5,0,0,0"
Template="{StaticResource UpdateButtonTemplate}"
Command="{Binding ButtonCommand}"
CommandParameter="Update"/>
</StackPanel>
</Border>
<Rectangle
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="2"
StrokeDashArray="0.5 1.0 0.3"
Stroke="LightGray"
Visibility="{Binding IsAddVisible}"/>
<Border
Grid.Row="0"
Grid.Column="2"
Width="230"
Visibility="{Binding IsAddVisible}">
<StackPanel>
<Border
Margin="3,3,3,0"
Style="{StaticResource PanelBorderStyle}">
<StackPanel>
<Label
Grid.Row="0"
Content="{Binding LabelText}"
Style="{StaticResource PanelLabelStyle}"/>
<TextBox
Grid.Row="1"
Text="{Binding NewLookup}"
Style="{StaticResource TextBoxStyle}"/>
<ItemsControl
Width="222"
Margin="0,0,-226,0"
HorizontalAlignment="Left"
ItemsSource="{Binding childAdditionalFields}"
ItemTemplate="{StaticResource AdditionalItemsTemplate}"/>
</StackPanel>
</Border>
<Button
Grid.Row="2"
Width="50"
HorizontalAlignment="Left"
Margin="3.5,0,0,0"
Template="{StaticResource AddButtonTemplate}"
Command="{Binding ButtonCommand}"
CommandParameter="Add"/>
</StackPanel>
</Border>
</Grid>
</Border>
In the resources, the AdditionalItemsTemplate loads a label for each column name, and the textbox is loaded with the values, or can be used to enter new values
I would like to make just ONE datepicker for ONE table.
So for example. In table Students, the fields are Name (varchar), Age (varchar), DateStarted (date). A textbox loads for Name and Age, but just for DateStarted a datepicker should load.
So far, in the resources, i add <local:DatePicker
SelectedDate="{Binding Value}"
Visibility="{Binding DateVisible}"/>
So that when the table Student is loaded, the visibility of the datepicker is changed from being hidden to visible.
Here is a sample of the ViewModel:
public AddUpdateConfigurationViewModel(TreeViewContainer treeViewContainer, List<AddChangeSiteConfigurationViewModel> lookupTypeList, FieldDataResponse fieldDataResponse)
{
NewLookup = String.Empty;
Width = 480;
DateVisible = "Hidden";
IsAddVisible = "Hidden";
IsUpdateEnabled = false;
if (treeViewContainer.AdditionalFields != null)
{
if (treeViewContainer.AdditionalFields.Count() > 0)
{
treeViewContainer.AdditionalFields.RemoveAll(x => x.Name.Contains("ID"));
treeViewContainer.AdditionalFields.RemoveAll(x => x.Name.Contains("Guid"));
}
}
if (treeViewContainer.AdditionalFields != null)
this.additionalFields = new ObservableCollection<TreeViewContainer>(treeViewContainer.AdditionalFields);
if ((treeViewContainer.ParentTable == null) | (treeViewContainer.ParentTable == String.Empty))
IsUpdateEnabled = true;
var lookupTypes = lookupTypeList.Where(x => x.Parent_Field == "ID" + treeViewContainer.TableName);
if (treeViewContainer.ParentTable == "IsParent")
{
IsAddVisible = "Visible";
DateVisible = "Hidden";
LabelText = "New " + treeViewContainer.Name;
TableName = treeViewContainer.TableName;
childAdditionalFields = new ObservableCollection<TreeViewContainer>();
foreach (var additionalField in treeViewContainer.AdditionalFields)
childAdditionalFields.Add(new TreeViewContainer(additionalField.Name));
if (treeViewContainer.Name.Equals("Student"))
{
DateVisible = "Visible";
}
}
else if (lookupTypes.Count() > 0)
{
foreach (var lookupType in lookupTypes)
{
IsAddVisible = "Visible";
LabelText = "New " + lookupType.Name;
TableName = lookupType.TableName;
childAdditionalFields = new ObservableCollection<TreeViewContainer>();
foreach (var additionalChildField in lookupType.additionalFieldsDictionary)
childAdditionalFields.Add(new TreeViewContainer(additionalChildField.Key));
}
}
else
Width = 250;
this.Name = treeViewContainer.Name;
this.Value = treeViewContainer.Name;
this.ID = treeViewContainer.ID;
this.treeViewContainer = treeViewContainer;
this.lookupTypeList = lookupTypeList;
this.fieldDataResponse = fieldDataResponse;
}
I hope this makes sense.
Now when i load the usercontrol and select on another table, sometimes the datepicker is visible, even though the visibility is set to Hidden.
And when the Student table is selected, there are datepickers under textboxes for every field.
How do I go about just making one datepicker for this one table?
If there is any more info needed, I will edit and update accordingly
EDIT
Screenshots
This is how the treeview loads. all the tables in the db are listed here
now clicking on a table that shouldn't have the datepicker does this:
it adds the datepicker to this treeview container. i want to eliminate this,
and this is where it should be
but i have too many, see. there should only be one added here. not all the way down, under every textbox.
and the Visible setting is just a temp fix, until i get this datepicker sorted. the visibility can be tackled later
I don't think adding a datepicker and trying to show/hide it is the best way to go. A better way is to load a datatemplate which matches the type which is being bound to. So if the column is a datetime type, you should load a datatemplate which has a label and a datepicker. This is what the DataTemplateSelector is for. You create a class which extends DataTemplateSelector, and decide which template to return, based on the object being bound to.
<UserControl.Resources>
<DataTemplate x:Key="DateTimeFieldTemplate">
<Border>
<StackPanel>
<Label
Content="{Binding Name}"
Style="{StaticResource PanelLabelStyle}"/>
<local:DatePicker
SelectedDate="{Binding Value}"/>
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="TextFieldTemplate">
<Border>
<StackPanel>
<Label
Content="{Binding Name}"
Style="{StaticResource PanelLabelStyle}"/>
<TextBox
Text="{Binding Value}"
Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
</Border>
</DataTemplate>
<local:TemplateSelector x:Key="TemplateSelector" DateTimeFieldTemplate="{StaticResource DateTimeFieldTemplate}" TextFieldTemplate="{StaticResource TextFieldTemplate}"/>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}" ItemTemplateSelector="{StaticResource TemplateSelector}"/>
</Grid>
You illustrate in your original code that you have a Name and Value property, so you only need to decide on how to provide information on what datatype the Value property is. One way is to make an interface with a Value property of type object:
public interface IBoundDataColumn
{
public string Name { get; } // Not really necessary for the template selector, but perhaps for completeness
public object Value { get; }
}
public class DateTimeColumn : IBoundDataColumn
{
public string Name { get; set; }
public object Value { get; set; }
}
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate TextFieldTemplate
{
get;
set;
}
public DataTemplate DateTimeFieldTemplate
{
get;
set;
}
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
IBoundDataColumn boundCol = item as IBoundDataColumn;
if (boundCol.Value.GetType() == typeof(DateTime))
{
return DateTimeFieldTemplate;
}
else
{
return TextFieldTemplate;
}
return base.SelectTemplate(item, container);
}
}
Or you could have a DataType property:
public interface IBoundDataColumn
{
public Type DataType {get;}
}
public class DateTimeColumn : IBoundDataColumn
{
public string Name {get;set;}
public DateTime Value {get;set;}
public Type DataType { get { return typeof(DateTime); } }
}
Or you could avoid the interface altogether and use reflection to get the type dynamically:
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
var p = item.GetType().GetProperty("Value");
if (t.PropertyType == typeof(DateTime))
...
}