|
Hello!
I was wondering how to populate the multicombobox from the backend and not by user, from the parent control.
Scenario, I am using your code base (Multi Select ComboBox in WPF[^]) for user to save items to database.
I now want to allow the user to pull all the items previously selected to re-populate the multicombobox.
I have been trying but with no success.
Many thanks on any advise you can provide!
James
modified 25-May-23 11:47am.
|
|
|
|
|
At the bottom of the linked article is a place to leave questions or comments to the author.
"the debugger doesn't tell me anything because this code compiles just fine" - random QA comment
"Facebook is where you tell lies to your friends. Twitter is where you tell the truth to strangers." - chriselst
"I don't drink any more... then again, I don't drink any less." - Mike Mullikins uncle
|
|
|
|
|
As Jeron said, the forum at the bottom of the article[^] is the place to post questions about the article.
However, the author hasn't been active since 2014, so it's possible he may not still be here.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
So I am continuing to work on my Navigation Control[^]. See this[^] and this[^]. The code is in this repo[^].
Special thanks to Richard Deeming for all your help so far. I've learned alot from you.
If you look at the image of the control in the first link above, you 'll see 3 sections, with the 3rd still loading. The expanders can be openend either by the user, or by saving & restoring a bool. On the NavigationPane control there's a DP called IsPaneExpanded. This get called with from the user or by the restoring during load.
When the control is being initialized I store the value of the DP.
Fields
private bool _isPaneExpanded = false;
private bool _isInitialized = false;
IsPaneExpanded DP
public static readonly DependencyProperty IsPaneExpandedProperty =
DependencyProperty.Register("IsPaneExpanded",
typeof(bool),
typeof(NavigationPane),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsPaneExpandedChanged)));
public bool IsPaneExpanded
{
get { return (bool)GetValue(IsPaneExpandedProperty); }
set { SetValue(IsPaneExpandedProperty, value); }
}
private static async void OnIsPaneExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (NavigationPane)d;
if (!control._isInitialized)
{
control._isPaneExpanded = control.IsPaneExpanded;
control.IsPaneExpanded = false;
}
else
{
if (control.IsPaneExpanded)
{
await control.Load();
}
}
}
Then, once initialzation is complete, I check that to see if the control's expander should be expanded.
Problem 1
The problem above is that the _isPaneExpanded is always false by the time it gets here. It IS being set to True in the DP, yet once the code reaches this point, it's false, so the load never happens. Maybe it's because the DP is static and the field isn't?
Problem 2
Another issue I see - I'd like the expander to open visuall FIRST, then call load. The Expander's Expanded event fires AFTER it has opened, but BEFORE the user sees anything visually the load is firing. Nothing changes visually until AFTER the load is complete.
private async void NavigationPane_Initialized(object? sender, EventArgs e)
{
if (_isPaneExpanded) <==================================================== PROBLEM 1 HERE
{
<=============================== PROBLEM 2 HERE. HOW DO YOU FORCE THE EXPANDER TO SHOW AS OPEN BEFORE LOAD
await Load();
}
_isInitialized = true;
}
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 14-May-23 16:37pm.
|
|
|
|
|
Problem 1:
The change event for the IsPaneExpanded property sets the IsPaneExpanded property to false if _isInitialized is false. If the property has just changed to true , that will cause the change event to fire again. As a result, the _isPaneExpanded field will be set to true , then immediately set back to false .
Thankfully, WPF won't fire the event if the property is set to the same value; otherwise, you'd end up with infinite recursion.
It would probably be better to use a CoerceValueCallback[^] instead:
public static readonly DependencyProperty IsPaneExpandedProperty =
DependencyProperty.Register("IsPaneExpanded",
typeof(bool),
typeof(NavigationPane),
new PropertyMetadata(
defaultValue: false,
propertyChangedCallback: new PropertyChangedCallback(OnIsPaneExpandedChanged),
coerceValueCallback: new CoerceValueCallback(CoerceIsPaneExpanded)
));
private static void OnIsPaneExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (NavigationPane)d;
control._isPaneExpanded = (bool)e.NewValue;
control.CoerceValue(IsPaneExpandedProperty);
if (control._isInitialized && control._isPaneExpanded)
{
_ = control.Load();
}
}
private static object CoerceIsPaneExpanded(DependencyObject d, object baseValue)
{
var control = (NavigationPane)d;
return control._isInitialized ? baseValue : (object)false;
}
Problem 2:
You need to change the _isInitialized field, trigger the change event for the IsPaneExpanded property, then load the data.
private void NavigationPane_Initialized(object? sender, EventArgs e)
{
_isInitialized = true;
CoerceValue(IsPaneExpandedProperty);
if (_isPaneExpanded)
{
_ = Load();
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks, I understand the change you recommened, but it doesn't seem to change anything. I have two different issues. I beleive I solved the first one.
- The Projects pane should be expanded on startup BEFORE it loads so the user seees the wait indicator. This bit of code in the MainWindow simulates storing and resetting it from settings later on. if there was an ExpandedExpanded (past-tense) then I could call Load() from there.
Main Window.cs
NavigationPaneInfos = new List<NavigationPaneModel>
{
new NavigationPaneModel
{
Header = "Projects",
NavigationItemType = NavigationItemType.Project,
DataSource = Functional.Apply(Repository.GetNavigationItems, NavigationItemType.Project),
IsExpanded = true
},
new NavigationPaneModel
{
Header = "Inventory",
NavigationItemType = NavigationItemType.Inventory,
DataSource = Functional.Apply(Repository.GetNavigationItems, NavigationItemType.Inventory),
},
new NavigationPaneModel
{
Header = "Companies" ,
NavigationItemType = NavigationItemType.Company,
DataSource = Functional.Apply(Repository.GetNavigationItems, NavigationItemType.Company),
},
new NavigationPaneModel
{
Header = "Employees",
NavigationItemType = NavigationItemType.Employee,
DataSource = Functional.Apply(Repository.GetNavigationItems, NavigationItemType.Employee),
}
};
so, that caused this
private static object CoerceIsPaneExpanded(DependencyObject d, object baseValue)
{
var control = (NavigationPane)d;
var newVal = control._isInitialized ? baseValue : (object)false;
return newVal;
}
and
private void NavigationPane_Initialized(object? sender, EventArgs e)
{
_isInitialized = true;
CoerceValue(IsPaneExpandedProperty);
if (_isPaneExpanded)
{
_ = Load();
}
}
so, I added this, which correctly sets _isPaneExpanded based off the value set in the main window
private static async void OnNavigationPaneModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (NavigationPane)d;
var model = e.NewValue as NavigationPaneModel;
if (model != null)
{
control._isPaneExpanded = model.IsExpanded;
}
}
This is where the UI is freezing up
private async Task Load()
{
if (NavigationPaneModel != null && NavigationPaneModel.DataSource != null)
{
var dataSource = NavigationPaneModel.DataSource();
List<NavigationEntity>? data = null;
if (dataSource != null)
{
data = await Task.Run(() => dataSource);
}
if (data != null)
{
Items = new ObservableCollection<NavigationEntity>(data);
}
}
}
so I tried this but....
private async Task Load()
{
if (NavigationPaneModel != null && NavigationPaneModel.DataSource != null)
{
List<NavigationEntity>? data = null;
await Task.Run(() =>
{
data = NavigationPaneModel.DataSource();
});
if (data != null)
{
Items = new ObservableCollection<NavigationEntity>(data);
}
}
}
So, it seems like all that's left is to get the backend call to fire async. The delegate was set in the UI using
NavigationPaneModel.DataSource = Functional.Apply(Repository.GetNavigationItems, NavigationItemType.Project)
How can I invoke the delegate async from Load()?
I checked my code into the repo.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I have a CustomControl which contains another custom control. I want to bubble a click event from the Inner Control and handle it on the Window.
Even better, can I somehow bind to the inner control's ButtonClicked command in the Window's VM?
I found this two SO article. It's close, but it doesn't address custom events:
https://stackoverflow.com/questions/65894582/subscribe-to-any-bubbled-up-wpf-child-control-routed-event-without-an-instance-o
Custom Controls
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BubblingEventsDemo"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors">
<Style TargetType="{x:Type local:InnerControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:InnerControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Button Content="Click Me"
Height="32"
Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ButtonClickedCommand,
RelativeSource={RelativeSource TemplatedParent}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:OuterControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:OuterControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<local:InnerControl Height="100"
Width="150"
Background="LightBlue"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Code Behind
using CommunityToolkit.Mvvm.Input;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace BubblingEventsDemo
{
public class InnerControl : Control
{
private ICommand? _ButtonClickedCommand;
public ICommand ButtonClickedCommand
{
get
{
if (_ButtonClickedCommand == null)
_ButtonClickedCommand = new RelayCommand(ButtonClickedExecuted, ButtonClickedCanExecute);
return _ButtonClickedCommand;
}
}
public static readonly RoutedEvent ButtonClickedEvent =
EventManager.RegisterRoutedEvent("ButtonClicked",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(InnerControl));
public event RoutedEventHandler ButtonClicked
{
add { AddHandler(ButtonClickedEvent, value); }
remove { RemoveHandler(ButtonClickedEvent, value); }
}
static InnerControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(InnerControl),
new FrameworkPropertyMetadata(typeof(InnerControl)));
}
private bool ButtonClickedCanExecute()
{
return true;
}
private void ButtonClickedExecuted()
{
RaiseEvent(new RoutedEventArgs(ButtonClickedEvent));
}
}
}
Window XAML
<Window x:Class="BubblingEventsDemo.UI.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ctrls="clr-namespace:BubblingEventsDemo;assembly=BubblingEventsDemo"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<ctrls:OuterControl Background="Tan"
Height="225"
Width="300"
ctrls:InnerControl.ButtonClicked="OuterControl_ButtonClicked"> <==== THIS CATCHES THE EVENT IN THE WINDOW'S CODE BEHIND
<i:Interaction.Triggers>
WHAT I WANT IS TO HANDLE IT IN THE VIEW MODEL. WHAT GOES HERE IN 'EventName'?
<i:EventTrigger EventName="OuterControl_ButtonClicked">
<i:InvokeCommandAction Command="{Binding ButtonClickedCommand, RelativeSource={RelativeSource TemplatedParent}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ctrls:OuterControl>
</Grid>
</Window>
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 12-May-23 11:15am.
|
|
|
|
|
Handling an event higher up is "tunneling"; i.e. "Preview..." handlers.
"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
|
|
|
|
|
|
Handling it "higher" up BEFORE it goes DOWN, is "tunneling".
The PARENT is the WINDOW ... and PREVIEW allows the PARENT to HANDLE the event before, or instead of, or in conjunction with ... the CHILD (i.e. the UC).
This is called TUNNELING.
"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
|
|
|
|
|
Bubbling vs Tunneling[^]
Again, I'm trying to handle an event in the Window that was invoked in the nested child control. The event is defined as Bubbling, therefore it goes UP the UI stack
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I have solved similar things before with the use of an attached behavior so you use that to bind the command to a property in the VM
|
|
|
|
|
Thanks. Do you have an example of that?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
You will need to install the NuGet:
NuGet Gallery | Microsoft.Xaml.Behaviors.Wpf 1.1.39[^]
Then create a class like this one:
public class ButtonAttatchment:Behavior<Button>
{
protected override void OnAttached()
{
AssociatedObject.Click += AssociatedObject_Click;
}
protected override void OnDetaching()
{
AssociatedObject.Click -= AssociatedObject_Click;
}
private void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (AssociatedObject != null)
{
((ButtonAttatchment)sender).IsSpinning = !((ButtonAttatchment)sender).IsSpinning;
}
}
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool),typeof(ButtonAttatchment), new UIPropertyMetadata(false));
public bool IsSpinning
{
get => (bool)GetValue(IsSpinningProperty);
set => SetValue(IsSpinningProperty, value);
}
}
This basically creates a code-behind implementation that you could use to fix complicated binding issues.
|
|
|
|
|
Thanks. I'll give it a shot
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
The XAML code is like this:
<Window x:Class="WPF_Tunneling_AttatchedBehavior.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:behavior="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:WPF_Tunneling_AttatchedBehavior"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Button Content="Test">
<behavior:Interaction.Behaviors>
<local:ButtonAttatchment x:Name="vm"/>
</behavior:Interaction.Behaviors>
</Button>
<TextBox Text="{Binding ElementName=vm,Path=IsSpinning}"></TextBox>
</StackPanel>
</Window>
And I made a slight mistake but Im sure you have figured it out by now:
private void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (AssociatedObject != null)
{
IsSpinning = !IsSpinning;
}
}
|
|
|
|
|
Thanks for the code, but I'm not sure if this solves my issue.
jFirst, see this image[^]. Second, see this[^]. It's almost what I want.
There's got to be a way to do this. I put a demo in this repo[^]. See the MainWIndow's XAML.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Ok, I don't really see what you are getting at but it can be solved extremley easy:
<Grid PreviewMouseDown="Grid_PreviewMouseDown">
<ctrls:OuterControl Background="Tan"
Height="225"
Width="300">
And
private void Grid_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.OriginalSource != null)
{
}
}
|
|
|
|
|
Referring to the first pic with all the nested controls, I have this Routed even deep inside MyNewControl
public static readonly RoutedEvent ItemSelectedEvent =
EventManager.RegisterRoutedEvent("ItemSelected",
RoutingStrategy.Tunnel,
typeof(RoutedEventHandler),
typeof(NavigationPane));
public event RoutedEventHandler ItemSelected
{
add { AddHandler(ItemSelectedEvent, value); }
remove { RemoveHandler(ItemSelectedEvent, value); }
}
private void RaiseItemSelectedEvent()
{
vars args = new SelectedItemEventArgs(ItemSelectedEvent, SelectedItem);
RaiseEvent(args);
}
How do I catch this event in the window? What is the syntax? Using some kind of Preview_... is going to catch a LOT of events. Is there no way to subscribe directly to that event from the Window?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I've got a listbox with expanders[^] in it.
The problem is that expander's header content is not stretched. I've tried all kinds of things, but I can't seem to get it. Here's the XAML
<Expander Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}}"
Margin="{Binding Margin, RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource TemplatedParent}}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
IsExpanded="{Binding IsPaneExpanded, RelativeSource={RelativeSource TemplatedParent}}">
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Header}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="1"/>
<Button Grid.Column="1"
Height="18"
Width="18"
Margin="0,1,1,1"/>
</Grid>
</Expander.Header>
<CONTENT REMOVED FOR BREVITY>
</Expander>
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Stretching Content in an Expander Header | Josh Smith on WPF[^]
The width binding trick won't work, as it will push the button outside of the available space. But the code-behind trick should be OK.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
That did it.
I saw this article already and tried the XAML approach. I should have hep reading.
Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Does anyone have any good example code that demonstrates drag and drop for LISTBOX and TREEVIEWs?
I need this using VB.NET WPF in specific!
Thanks for the help!
|
|
|
|
|
|
|