|
I have a tab control, and I have a header template and style:
<!Tab Item Header Template>
<DataTemplate x:Key="CustomHeaderTemplate">
<DockPanel LastChildFill="True">
<TextBlock Text="{Binding FileName}"
VerticalAlignment="Center"
FontSize="14"/>
<Button Command="{Binding DataContext.CloseTabCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
VerticalAlignment="Center"
Height="22"
Width="22"
Background="SteelBlue"
Margin="3,0,0,0">
<Path Data="M1,9 L9,1 M1,1 L9,9"
Stroke="White"
StrokeThickness="2" />
</Button>
</DockPanel>
</DataTemplate>
<!Tab Item Style>
<Style TargetType="TabItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" >
<MenuItem Header="Close Tab"
Command="{Binding Source={StaticResource Proxy}, Path=Data.CloseTabCommand}"
CommandParameter="{Binding}"/>
<MenuItem Header="Close All Tabs"
Command="{Binding Source={StaticResource Proxy}, Path=Data.CloseAllTabsCommand}"
CommandParameter="{Binding}"/>
<MenuItem Header="Close All But This"
Command="{Binding Source={StaticResource Proxy}, Path=Data.CloseAllButThisTabCommand}"
CommandParameter="{Binding}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
The problem is that if I right-click a tab that is NOT selected, and choose one of the close options, the SELECTED tab is closed. How can I specify that correct tab item in the command?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
I have a window with bound fields that I'm validating with INotifyDataErrorInfo. There is a button on the window that opens a dialog that the user needs to complete before all of the data is truly valid.
What I'd like is to to have a validation message next to the button. But since the button is not bound, I can't really use INotifyDataErrorInfo for this.
What's the right way to do this?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
modified 3-Jan-24 1:29am.
|
|
|
|
|
I have a CustomControl with three DP's. On startup the change callbacks fire for all three, but I need to know when they have ALL fired, so I can then set up the control using their data. The Initialized gets called first, then the DP's, so I can't rely on that.
I was thinking of creating a bool flag for each, and when each are true, then go ahead and load my control using all the DP's, but I'm wondering if there's another way.
An event that fires AFTER the DP's would be nice, but I don't see one.
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
If I understand what you're looking for, I use the control's Loaded event. At that point the control is ready to go and all of the Dependency properties should be set.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
I have an app that displays 1 to 16 video player controls. The user can select the type of player (ie LeadTools, VLC) and the number of players to show.
Once the type and number of players is selected, I load the players into the main window's Players collection. The ActiveX player is hosted in a PlayerHostViewl, and that is added to an ObservableCollection<playerhostviewmodel> called 'Players'
Here's the main window's XAML's resources, showing the data template for the collection:
<Window.Resources>
<DataTemplate DataType="{x:Type vms:PlayerHostViewModel}">
<vws:PlayerHostView/>
</DataTemplate>
</Window.Resources>
and the MainWindow's player collection:
<Border Grid.Row="1"
Grid.Column="0"
BorderThickness="1"
BorderBrush="SteelBlue"
Margin="5,0,0,5">
<ItemsControl ItemsSource="{Binding Players}"
Margin="2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True"
Margin="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
The MainWindowViewModel's LoadPlayers method:
private void LoadPlayers()
{
if (SelectedPlayerInfo != null && SelectedPlayerCount > -1)
{
for (int i = 0; i < SelectedPlayerCount + 1; i++)
{
var playerAssembly = (IPlayer)Activator.CreateInstance(SelectedPlayerInfo.PlayerType);
var player = new PlayerHostViewModel(playerAssembly, $"Player {i + 1}", LiveStream, DVRStream);
Players.Add(player);
}
}
}
Here's the PlayerHost CTORs
public PlayerHostViewModel()
{
}
public PlayerHostViewModel(IPlayer player, string playerName, string liveStream, string dvrStream)
{
Player = player;
PlayerName = playerName;
LiveStream = LiveStream;
DVRStream = dvrStream;
}
The problem is that the PlayerHost paramterized CTOR fires when this line executes, as it should:
var player = new PlayerHostViewModel(playerAssembly, $"Player {i + 1}", LiveStream, DVRStream);
but then the default CTOR fires when this line executes, resetting all the properties the previous CTOR initialed
Players.Add(player);
I've used DataTemplates in plenty of other apps. I don't understand what's different this time. Anyone see what's wrong?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
It sounds like you're creating two instances of the viewmodel. What does the PlayerHostView look like?
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I have a sample app with a AddingNewItem command for a DataGrid, but I can't get it to fire.
<i:Interaction.Triggers>
<i:EventTrigger EventName="AddingNewItem">
<i:InvokeCommandAction Command="{Binding DataContext.NewItemAddedCommand,
RelativeSource={
RelativeSource FindAncestor, AncestorType=Window}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
There are no binding errors or other problems. It just doesn't fire. The Setter for the command do fire.
My sample project is here [^] . Does anyone see what's wrong?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
modified 1-Dec-23 21:19pm.
|
|
|
|
|
Without seeing the full XAML and ViewModel code, it's a bit challenging to figure out where your problem lies. More info is required to try and find a solution, no, we will not be opening your entire project to try and find a solution.
|
|
|
|
|
Andre Oosthuizen wrote: no, we will not be opening your entire project to try and find a solution.
Plenty of others have.
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
I have a DataGrid that allows users to define CSV file columns for a UI I'm creating. It's bound to an ObservableCollection<columninfoentity>. I have CanUserAddRows = true. Apparantly setting this to True exposes some strange bug.
The grid's first column has a button:
<DataGrid Grid.Row="2"
Grid.Column="0"
ItemsSource="{Binding ColumnInfos}"
HorizontalGridLinesBrush="LightGray"
VerticalGridLinesBrush="LightGray"
AutoGenerateColumns="False"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
BorderBrush="SteelBlue"
BorderThickness="1"
HeadersVisibility="Column"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
VerticalAlignment="Stretch"
Margin="5,0,5,5">
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding IsColumnWidthVisibile}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding DeleteColumnCommand, RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1, AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
Margin="2"
Height="18"
Width="18"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Column Name"
Binding="{Binding ColumnName}"
Width="*"/>
<DataGridTextColumn Header="Width"
Binding="{Binding ColumnWidth}"
Width="65"
Visibility="{Binding Data, Converter={StaticResource boolToVisibilityConverter}, Source={StaticResource proxy}}"/>
</DataGrid.Columns>
</DataGrid>
When I enable CanUserAddRows it and run the app I get
System.InvalidCastException: 'Unable to cast object of type 'MS.Internal.NamedObject' to type 'WPFDataGrid.ColumnInfoEntity'.'
Strangly, the exception is thrown in the RelayCommand's CanExecute.
If I comment out the delete button everything works fine.
I created a sample project[^] to demonstrate this.
I don't understand what the error means, or why I'm getting it. Any help would be great.
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
modified 28-Nov-23 17:51pm.
|
|
|
|
|
The problem is in your RelayCommand<T> class, which indiscriminately tries to cast the command parameter to the specified type.
Your DeleteCommand is an instance of RelayCommand<ColumnInfoEntity> , so it will try to cast the parameter to the ColumnInfoEntity . But for the "new item" row, the parameter will be passed the CollectionView.NewItemPlaceholder[^] value, which will be an MS.Internal.NamedObject instance. Hence the cast will fail, and your code will crash.
Change the RelayCommand<T> to test whether the parameter is the expected type:
private bool CanExecuteCore(T parameter)
{
return _canExecute is null || _canExecute(parameter);
}
public bool CanExecute(object parameter)
{
return parameter is T param && CanExecuteCore(param);
}
public void Execute(object parameter)
{
if (parameter is T param && CanExecuteCore(param))
{
_execute(param);
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
That did it. Thank you!
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
OK, so that works fine now.
Now I added an event trigger to the grid:
<i:EventTrigger EventName="AddingNewItem">
<i:InvokeCommandAction Command="{Binding NewItemAddedCommand}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
When I run this, I get the exception
System.InvalidOperationException: 'Items collection must be empty before using ItemsSource.'
It's thrown in the class ItemCollection.cs.
The ItemSource is set to an ObservableCollection which is loaded in the CTOR.
This is why I hate the DataGrid.
I commited this change. Any thoughts on this?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
You've added the <i:EventTrigger> as a direct child of the grid. Looking at the documentation[^], it should be wrapped in an <i:Interation.Triggers> element instead.
<DataGrid ...>
<DataGrid.Resources>
...
</DataGrid.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="AddingNewItem">
<i:InvokeCommandAction Command="{Binding NewItemAddedCommand}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
duh. Too many late nights
Thanks
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
I've asked about this already, but I'm not really getting it. I'm asking this again with more detail in the hopes that it'll make more sense.
Lets say I develop a custom control that have a TextBlock over a TextBox[^]. I have defind colors for Normal and Mouse Over
Here's the XAML for my control. It's pretty simple:
<pre><ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyTextBoxEx">
<pre>
<SolidColorBrush x:Key="CaptionNormalColor" Color="Green"/>
<SolidColorBrush x:Key="CaptionMouseOverColor" Color="Purple"/>
<SolidColorBrush x:Key="TextNormalColor" Color="Red"/>
<SolidColorBrush x:Key="TextMouseOverColor" Color="Blue"/>
<Style TargetType="{x:Type local:TextBoxEx}">
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TextBoxEx}">
<StackPanel Orientation="Vertical">
<Border Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}}"
Margin="{Binding Margin, RelativeSource={RelativeSource TemplatedParent}}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="caption"
Foreground="{StaticResource CaptionNormalColor}"
Text="{TemplateBinding Caption}"/>
<TextBox x:Name="text"
Foreground="{StaticResource TextNormalColor}"
Text="{TemplateBinding Text}"/>
</StackPanel>
</Border>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="caption" Property="TextElement.Foreground" Value="{StaticResource CaptionMouseOverColor}"/>
<Setter TargetName="caption" Property="TextElement.FontWeight" Value="Bold"/>
<Setter TargetName="text" Property="TextElement.Foreground" Value="{StaticResource TextMouseOverColor}"/>
<Setter TargetName="text" Property="TextElement.FontStyle" Value="Italic"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the question. I have assigned colors for normal, and for the Mouse Over triggers. Assume this control was in a compiled assembly, and I gave it to you, how would you go about overwriting the theme colors, or theming it the way you wanted?
When you buy a third party control, and want apply your theme to it, how do the controls inside get their colors set? In my case, the triggers are set to a specifically named brush. Just knowing the brush names wouldn't help you overwrite the theme colors, would it?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
Making color changes to parts of controls that already have "part resources" defined in generic.xaml is not customizing; it is annoying.
"Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I
|
|
|
|
|
You're missing the point.
When you buy a third party control, like say from Infragistics, and you use it in your app, when you write your own theme the third party control respects it.
I'm trying to understand how to make my control work the same way
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
I am implementing INotifyDataErrorInfo. Here's my base ViewModel
public class _ViewModelBase : BindableBase, INotifyDataErrorInfo
{
#region Events
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
#endregion
#region Private Fields
public readonly Dictionary<string, List<string>> _validationErrors = new Dictionary<string, List<string>>();
#endregion
#region Properties
public bool HasErrors => _validationErrors.Any();
#endregion
#region Public Methods
public IEnumerable GetErrors(string propertyName)
{
return _validationErrors.ContainsKey(propertyName) ? _validationErrors[propertyName] : null;
}
#endregion
#region Protected Methods
protected void AddError(string propertyName, string error)
{
if (!_validationErrors.ContainsKey(propertyName))
{
_validationErrors[propertyName] = new List<string>();
}
if (!_validationErrors[propertyName].Contains(error))
{
_validationErrors[propertyName].Add(error);
RaiseErrorsChanged(propertyName);
}
}
protected void ClearErrors(string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
{
_validationErrors.Remove(propertyName);
{
RaiseErrorsChanged(propertyName);
}
}
}
#endregion
#region Private Methods
private void RaiseErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
#endregion
}
Here's my CustomerView
<TextBlock Grid.Row="0"
Text="Customer Name"/>
<TextBox Grid.Row="1"
Grid.Column="0"
Text="{Binding CustomerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
IsEnabled="{Binding AreFieldsEnabled}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox" />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Here's my CustomerViewModel (parts ommited)
private void Validate()
{
ClearErrors(nameof(Customer.CustomerName));
if (string.IsNullOrWhiteSpace(Customer.CustomerName))
{
AddError(nameof(Customer.CustomerName), "The Customer Name cannot be empty.");
}
}
private void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
IsChanged = true;
Validate();
RaiseChangedEvent();
}
The problem is that if I use complex nation, as in Customer.CustomerName instead of just CustomerName, the validation text does not appear. The error gets added to the collection, but I don't see it.
What's wrong here?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
That makes no sense - both nameof(Customer.CustomerName) and nameof(CustomerName) will compile to the constant string "CustomerName" .
Are you sure that the view-model property is spelled correctly?
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
OK, so I'm not really sure what the problem is here:
private void Validate()
{
ClearErrors("CustomerName");
if (string.IsNullOrWhiteSpace(Customer.CustomerName))
{
AddError("CustomerName", "The Customer Name cannot be empty.");
}
}
<TextBox Grid.Row="1"
Grid.Column="0"
Text="{Binding Customer.CustomerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
IsEnabled="{Binding AreFieldsEnabled}">
<pre>
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox" />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
The TextBox is bound to Customer.CustomerName. When I remove the text, the error is added, but I don't see anything. I'm guessing I'm not doing this right.
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
The errors don't match the property you're binding to.
Try using:
const string propertyName = $"{nameof(Customer)}.{nameof(Customer.CustomerName)}";
ClearErrors(propertyName);
f (string.IsNullOrWhiteSpace(Customer.CustomerName))
{
AddError(propertyName, "The Customer Name cannot be empty.");
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Being "in error" is the state of the object in question; not a flag or entry in a list. That's transaction oriented versus "online".
My "HasErrors" runs all the "validations" on the object and returns true or false anytime it is called (e.g. before saving). No "error lists" to clear etc.
You can also retrieve all errors (to show only the first, for example, to avoid overloading thee user).
At the same time, all the textboxes in error are "red lined" as expected.
"Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I
|
|
|
|
|
See my latest reply to Richard
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
|