|
Apologies for the shouting but this is important.
When answering a question please:
- Read the question carefully
- Understand that English isn't everyone's first language so be lenient of bad spelling and grammar
- If a question is poorly phrased then either ask for clarification, ignore it, or mark it down. Insults are not welcome
- If the question is inappropriate then click the 'vote to remove message' button
Insults, slap-downs and sarcasm aren't welcome. Let's work to help developers, not make them feel stupid.
cheers,
Chris Maunder
The Code Project Co-founder
Microsoft C++ MVP
|
|
|
|
|
For those new to message boards please try to follow a few simple rules when posting your question.- Choose the correct forum for your message. Posting a VB.NET question in the C++ forum will end in tears.
- Be specific! Don't ask "can someone send me the code to create an application that does 'X'. Pinpoint exactly what it is you need help with.
- Keep the subject line brief, but descriptive. eg "File Serialization problem"
- Keep the question as brief as possible. If you have to include code, include the smallest snippet of code you can.
- Be careful when including code that you haven't made a typo. Typing mistakes can become the focal point instead of the actual question you asked.
- Do not remove or empty a message if others have replied. Keep the thread intact and available for others to search and read. If your problem was answered then edit your message and add "[Solved]" to the subject line of the original post, and cast an approval vote to the one or several answers that really helped you.
- If you are posting source code with your question, place it inside <pre></pre> tags. We advise you also check the "Encode "<" (and other HTML) characters when pasting" checkbox before pasting anything inside the PRE block, and make sure "Use HTML in this post" check box is checked.
- Be courteous and DON'T SHOUT. Everyone here helps because they enjoy helping others, not because it's their job.
- Please do not post links to your question into an unrelated forum such as the lounge. It will be deleted. Likewise, do not post the same question in more than one forum.
- Do not be abusive, offensive, inappropriate or harass anyone on the boards. Doing so will get you kicked off and banned. Play nice.
- If you have a school or university assignment, assume that your teacher or lecturer is also reading these forums.
- No advertising or soliciting.
- We reserve the right to move your posts to a more appropriate forum or to delete anything deemed inappropriate or illegal.
cheers,
Chris Maunder
The Code Project Co-founder
Microsoft C++ MVP
|
|
|
|
|
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
|
|
|
|
|
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.
|
|
|
|
|