|
Is there any way to specify a text value when either all items or no items are checked?
In my case I'm using the CheckcComboBox in a report arguments window, so selecting none is the same as all. What I want is to show the word 'All' by default. If any items are selected, then I'd like it to work as it does and show a comma delimited list of checked items.
I have IsSelectAllActive = true in the XAML. I've also tried setting the Text property in the COllectionChanged event. Neither worked.
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 21-Jun-23 0:33am.
|
|
|
|
|
There's nothing obvious in the (rather limited) documentation[^]; you might need to raise an issue with them[^].
Or, if you've paid for the "plus" edition, use their "priority support" system.
NB: The "free" edition no longer supports commercial use[^]. If you're using it in anything other than a personal or internal-use product, they expect you to pay for it. However, there are a few forks from before the license change which are still properly "free".
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Wonderful. 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 need to load a large set of images and let the user pick from them. The user will select a folder, and the list should load. A view like this[^] is what I'd like it to look like.
The user should be able to filter the types of images to load. Preferable, I'd like to be able to have tabs for each folder so they can load multiple folders at once.
I'm not sure of the right container to use for this. Would a VirtualizingWrapPanel work for this?
Also, what's the right way to load the list? When the user selects the folder, I'd like it to start loading right away. Getting a list of file names is easy, but then I would probably need some kind of model class to bind to the list.
I'd like to hear what you guys think.
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 have a WPF that's branded four ways, say App1, App2, App3, and App4.
I change the name of the output assembly like this:
<AssemblyName Condition="'$(Configuration)' == 'Release App1'">App1</AssemblyName>
<AssemblyName Condition="'$(Configuration)' == 'Release App2'">App2</AssemblyName>
<AssemblyName Condition="'$(Configuration)' == 'Release App3'">App3</AssemblyName>
<AssemblyName Condition="'$(Configuration)' == 'Release App4'">App4</AssemblyName>
This results in the compiled assembly being named according to the brand.
I have images all over the app like this:
<Image x:Name="myImage"
Stretch="None"
Width="15"
Height="15"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="/App1;component/Images/My_Image.png" />
In some cases, in Release builds, the images fail to appear. From what I can see, in Release builds the source can't find the assembly name, so the image fails to load. I verified this by renaming all of the output assemblies in the <assemblyname> tags above to "App1" and it works.
So I've implemented code like this:
private BitmapImage _MyImage;
public BitmapImage MyImage
{
get { return _MyImage; }
set
{
if (_MyImage != value)
{
_MyImage = value;
RaisePropertyChanged("MyImage");
}
}
}
and
private void SetupBranding()
{
var imageFolder = "";
if BRAND_APP1
imageFolder = "App1";
elif BRAND_APP2
imageFolder = "App2";
elif BRAND_APP3
imageFolder = "App3";
elif BRAND_APP4
imageFolder = "App4";
<h1>endif</h1>
string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
var path = $"/{assemblyName};component/Images/Brand/{imageFolder}/My_Image.png";
MyImage = new BitmapImage(new Uri(path, UriKind.Relative));
}
This seems to work fine, but I have do do this anywhere I want to use brand-specific images. I'm wondering of there's a better way? Is there a way to handle hard coded pack URI such has:
Source="/App1;component/Images/My_Image.png"
One other concern is that, while most of this is done in the Windows and Views, there are some URI's specified in styles.
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.
|
|
|
|
|
Use relative Pack URI's.
Pack URIs - WPF .NET Framework | Microsoft Learn
"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
|
|
|
|
|
Good morning all,
been writing a little program that when you put a spotify playlist id in it will get the whole playlist
want the name and total songs in the playlist mainly so all songs in the playlist and then total count of all songs
with a bit of hard work managed to get it to deserialize the content but cant seem to get it to return that
what i have currently is this,
so it goes though and get as far as here,
json data returned from spotify api
put it in pastebin link as contains a lot of data
then thought i would use json2c#
to create my structure for me,
and have this in a playlist.cs
<pre lang="C#"><pre>using System;
using System.Collections.Generic;
using System.Text;
namespace Dark_Admin_Panel.Model
{
public class Playlist
{
public class AddedBy
{
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Album
{
public string album_type { get; set; }
public List<Artist> artists { get; set; }
public List<string> available_markets { get; set; }
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public List<Image> images { get; set; }
public string name { get; set; }
public string release_date { get; set; }
public string release_date_precision { get; set; }
public int total_tracks { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Artist
{
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public string name { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class ExternalIds
{
public string isrc { get; set; }
}
public class ExternalUrls
{
public string spotify { get; set; }
}
public class Followers
{
public object href { get; set; }
public int total { get; set; }
}
public class Image
{
public int height { get; set; }
public string url { get; set; }
public int width { get; set; }
}
public class Item
{
public DateTime added_at { get; set; }
public AddedBy added_by { get; set; }
public bool is_local { get; set; }
public object primary_color { get; set; }
public Track track { get; set; }
public VideoThumbnail video_thumbnail { get; set; }
}
public class Owner
{
public string display_name { get; set; }
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Root
{
public bool collaborative { get; set; }
public string description { get; set; }
public ExternalUrls external_urls { get; set; }
public Followers followers { get; set; }
public string href { get; set; }
public string id { get; set; }
public List<Image> images { get; set; }
public string name { get; set; }
public Owner owner { get; set; }
public object primary_color { get; set; }
public bool @public { get; set; }
public string snapshot_id { get; set; }
public Tracks tracks { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Track
{
public Album album { get; set; }
public List<Artist> artists { get; set; }
public List<string> available_markets { get; set; }
public int disc_number { get; set; }
public int duration_ms { get; set; }
public bool episode { get; set; }
public bool @explicit { get; set; }
public ExternalIds external_ids { get; set; }
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public bool is_local { get; set; }
public string name { get; set; }
public int popularity { get; set; }
public string preview_url { get; set; }
public bool track { get; set; }
public int track_number { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Tracks
{
public string href { get; set; }
public List<Item> items { get; set; }
public int limit { get; set; }
public object next { get; set; }
public int offset { get; set; }
public object previous { get; set; }
public int total { get; set; }
}
public class VideoThumbnail
{
public object url { get; set; }
}
}
}
and is called via the spotifySearch methord
<pre>using System;
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Text;
using static Dark_Admin_Panel.Model.SpotifySearch;
namespace Dark_Admin_Panel.Model
{
public class SpotifySearch
{
public class ExternalUrls
{
public string spotify { get; set; }
}
public class Followers
{
public object href { get; set; }
public int total { get; set; }
}
public class ImageSP
{
public int width { get; set; }
public string url { get; set; }
public int height { get; set; }
}
public class Item
{
public ExternalUrls external_urls { get; set; }
public Followers followers { get; set; }
public List<string> genres { get; set; }
public string id { get; set; }
public List<ImageSP> images { get; set; }
public string name { get; set; }
public int popularity { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Artists
{
public string href { get; set; }
public List<Item> items { get; set; }
public int limit { get; set; }
}
public class PlaylistItem
{
public ExternalUrls external_urls { get; set; }
public string id { get; set; }
public string name { get; set; }
public string type { get; set; }
public List<ImageSP> images { get; set; }
public Tracks tracks { get; set; }
public string uri { get; set; }
}
public class PlaylistContainer
{
public string href { get; set; }
public List<PlaylistItem> items { get; set; }
public int total { get; set; }
public int limit { get; set; }
}
public class Tracks
{
public string href { get; set; }
public int total { get; set; }
}
public class SpotifyResult
{
public PlaylistContainer playlists { get; set; }
public class AddedBy
{
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Album
{
public string album_type { get; set; }
public List<Artist> artists { get; set; }
public List<string> available_markets { get; set; }
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public List<Image> images { get; set; }
public string name { get; set; }
public string release_date { get; set; }
public string release_date_precision { get; set; }
public int total_tracks { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Artist
{
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public string name { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class ExternalIds
{
public string isrc { get; set; }
}
public class ExternalUrls
{
public string spotify { get; set; }
}
public class Followers
{
public object href { get; set; }
public int total { get; set; }
}
public class Image
{
public int height { get; set; }
public string url { get; set; }
public int width { get; set; }
}
public class Item
{
public DateTime added_at { get; set; }
public AddedBy added_by { get; set; }
public bool is_local { get; set; }
public object primary_color { get; set; }
public Track track { get; set; }
public VideoThumbnail video_thumbnail { get; set; }
}
public class Owner
{
public string display_name { get; set; }
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Root
{
public bool collaborative { get; set; }
public string description { get; set; }
public ExternalUrls external_urls { get; set; }
public Followers followers { get; set; }
public string href { get; set; }
public string id { get; set; }
public List<Image> images { get; set; }
public string name { get; set; }
public Owner owner { get; set; }
public object primary_color { get; set; }
public bool @public { get; set; }
public string snapshot_id { get; set; }
public Tracks tracks { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Track
{
public Album album { get; set; }
public List<Artist> artists { get; set; }
public List<string> available_markets { get; set; }
public int disc_number { get; set; }
public int duration_ms { get; set; }
public bool episode { get; set; }
public bool @explicit { get; set; }
public ExternalIds external_ids { get; set; }
public ExternalUrls external_urls { get; set; }
public string href { get; set; }
public string id { get; set; }
public bool is_local { get; set; }
public string name { get; set; }
public int popularity { get; set; }
public string preview_url { get; set; }
public bool track { get; set; }
public int track_number { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Tracks
{
public string href { get; set; }
public List<Item> items { get; set; }
public int limit { get; set; }
public object next { get; set; }
public int offset { get; set; }
public object previous { get; set; }
public int total { get; set; }
}
public class VideoThumbnail
{
public object url { get; set; }
}
public class SpotifySearch
{
public class ExternalUrls
{
public string spotify { get; set; }
}
public class Followers
{
public object href { get; set; }
public int total { get; set; }
}
public class ImageSP
{
public int width { get; set; }
public string url { get; set; }
public int height { get; set; }
}
public class Item
{
public ExternalUrls external_urls { get; set; }
public Followers followers { get; set; }
public List<string> genres { get; set; }
public string id { get; set; }
public List<ImageSP> images { get; set; }
public string name { get; set; }
public int popularity { get; set; }
public string type { get; set; }
public string uri { get; set; }
}
public class Artists
{
public string href { get; set; }
public List<Item> items { get; set; }
public int limit { get; set; }
}
public class PlaylistItem
{
public ExternalUrls external_urls { get; set; }
public string id { get; set; }
public string name { get; set; }
public string type { get; set; }
public List<ImageSP> images { get; set; }
public Tracks tracks { get; set; }
public string uri { get; set; }
}
public class PlaylistContainer
{
public string href { get; set; }
public List<PlaylistItem> items { get; set; }
public int total { get; set; }
public int limit { get; set; }
}
public class Tracks
{
public string href { get; set; }
public int total { get; set; }
}
public class SpotifyResult
{
public PlaylistContainer playlists { get; set; }
}
}
}
}
}
then when the item is searched it uses
search helper
and this is where it returns nothing
however the responce.Content returns all the values,
search helper
<pre>public static SpotifySearch.SpotifyResult SearchArtistOrSong(string searchword)
{
var client = new RestClient("https://api.spotify.com/v1/playlists");
client.AddDefaultHeader("Authorization", $"Bearer {token.access_token}");
var request = new RestRequest($"{searchword}", Method.Get);
var response = client.Execute(request);
if (response.IsSuccessful)
{
var result = JsonConvert.DeserializeObject<SpotifySearch.SpotifyResult>(response.Content);
return result;
}
else
{
return null;
}
}
and the mainwindow.cs
<pre> private void elfentext_KeyUp(object sender, KeyEventArgs e)
{
var result = SearchHelper.SearchArtistOrSong(elfentext.Text);
if (result == null)
{
return;
}
var listArtist = new List<SpotifySearch.PlaylistItem>();
if (result?.playlists?.items != null)
{
foreach (var item in result.playlists.items)
{
var images = item.images.Any() ? item.images : new List<SpotifySearch.ImageSP>();
listArtist.Add(new SpotifySearch.PlaylistItem()
{
external_urls = item.external_urls,
id = item.id,
name = item.name,
type = item.type,
images = images,
tracks = item.tracks,
uri = item.uri
});
}
}
elfenlist.ItemsSource = listArtist;
}
i managed to get artist working fine before but just playlist returns so much i just not quite sure what i am doing wrong any help would be so much appreciated
thanks in advance elfenliedtopfan5
|
|
|
|
|
You have asked this in a forum dedicated to WPF. Your post has nothing to do with this. You should ask this in Q&A.
|
|
|
|
|
elfenliedtopfan5 wrote: what i am doing wrong
Perhaps something is eating an exception?
Not clear how you determined that the response content is valid json.
You can also experiment (test) by populating everything in your json objects. Then serialize them to a string. Output that (so you have an example) then deserialize the same thing.
|
|
|
|
|
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;
}
}
|
|
|
|
|