WPF ContentControl content not getting parent Resources - c#

I'm currently getting to grips with WPF resources and wondering if you can help me:
I have a WPF window which contains its own resources. It also contains a content control which changes its content depending on what is selected in a tree view, e.g.:
contentControl1.Content = someUserControl;
This can be a UserControl e.g. SomeUserControl which uses a static resource which I have defined in the window xaml.
When creating an instance of SomeUserControl in the window code behind, I get an XMLParseException ('Provide value on 'System.Windows.StaticResourceExtension' threw an exception.'). This is on the line containing the binding to the static resource.
To solve this, I added this to the constructor of SomeUserControl (parentResources being the window's resources from where someUserControl is instantiated):
public SomeUserControl(ResourceDictionary parentResources)
{
this.Resources.MergedDictionaries.Add(parentResources);
InitializeComponent();
}
Is this the best approach for finding resources in this particular case? Thanks for any help.

I assume you have a {StaticResource myResource}. Have you tried using {DynamicResource myResource}?
See http://msdn.microsoft.com/en-us/library/ms748942.aspx
EDIT
Ok, given that you're resource is a converter, which cannot use a dynamic resource, then I think what you're doing is probably as a good a solution as any.
The underlying issue is that at the moment that your control is instantiated, it is not a part of the Window and so it doesn't have any access to the Window's resources. And as the term static in StaticResource implies, the control expects that the resource is going to be available. So you have to make it available when the control is instantiated.
You might also want to look at something like Prism and it's Region's which might be a better way to handle swapping out your "controls" (depending on your needs).

It depends on what resource you're talking about, actually.
Cause the way you do it could be pretty fine, with only drawback, that you detach yourself from declarative programming, which is expected way of coding in WPF.
You can also add a StaticResource, for example in App.xaml, so it will be initialized and loaded as soon as your app starts, and in any way, before window load.
Hope this helps.

Related

MahApps Metro DialogCoordinator: Display Dialog to span UserControl only (instead of entire window)?

I'm epxloring different ways to best show dialog windows in my application.
MahApp Metro's IDialogCoordinator seems quite useful, but I couldn't quite adjust it to my use case yet.
Say I'm creating a UserControl (view), whose ViewModel needs to be able to display dialogues.
These dialogues should, when displayed, overlay/span the UserControl only, NOT the entire Window in which the UserControl is hosted.
Is there any way to achieve this?
Default behavior always seems to span over the entire window, and I haven't found any way to change this yet.
So far, I've been using the Dialog coordinator in a very straightforward way, doing the following in my view:
<UserControl
xmlns:Dialog="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
Dialog:DialogParticipation.Register="{Binding}">
and set set the instance in my view's constructor by,
viewModel.Initialize(DialogCoordinator.Instance);
which I'd then call in the viewmodel via
IDialogCoordinator _DialogCoordinator; // set with viewModel.Initialize() called from the view
private async Task _SomeCmdExecute()
{
await _DialogCoordinator.ShowMessageAsync(this, "HEADER", "TEST");
}
Thanks!
Dialogs in MahApps.Metro are always at the window level (see the container PART_MetroActiveDialogContainer in the window's style.)
What you can do is changing the styling of dialogs, so they don't stretch horizontally accross the entire window. See the default template MetroDialogTemplate for reference.

WPF Navigation and Rotating Backgrounds

I'm working on an application, and I'm using the MVVM approach.
Basically, there are currently two Pages, and 1 MainWindow.
I switch between the pages using a Frame inside MainWindow.
In the main window, there are 2 buttons which are basically global and should show in all pages; x (exit) and settings.
This is basically my 'shell', as I decided to not use a window border.
The problem is I'd like each page to have a different background and this is where it gets complicated:
- Settings page: Grey background.
- Main Page: Rotating background color that changes according to a property.
The thing is the background is being set in the main window, because it should apply to the global area as well (the top, where the exit and settings buttons are).
I first set the background (in MainWindow) as bound to a property the represents the current page (the value is then being translated into a color hex code with the help of a converter).
All in all, this results in a case where the background changes when a page is changed, but not when the property inside MainPage changes. I can clearly understand why, but I have no idea how to solve it.
The possible solutions I came up with so far:
Somehow causing the binding in MainWindow to update/refresh when the property is changed in MainPage.
Changing the background manually from inside each of the pages. (Although doesn't it negate the idea of mvvm?)
Move the background into each of the pages and set it from there, while making the global buttons on top of the page (which could be a bad thing in case controls end up overlapping).
If so, what would be the best solution to this problem?
If you haven't already, I'd suggest you install some package via NuGet to make MVVM style development more enjoyable. I personally prefer MVVMLight which is... well, light, but it also packs lot's of helpful features.
To communicate between ViewModels, you have (at least) two possible approaches.
1) ViewModelLocator (not recommended)
ViewModelLocator is central place holding references to all of your viewmodels. You could add a property that is then used by all of the viewmodels to get/set the background.
....
x:Name="Main"
DataContext="{Binding Source={StaticResource Locator}, Path=MainVM}">
....
<Grid Background="{Binding Background, Converter={StaticResource StringBrushConverter}}">
...
2) Messenger (recommended)
When ever property changes in your viewmodel(s) or method is executed, you could send a message that your MainViewModel is registered to listen to. Sending a message would be as easy as...
Messenger.Default.Send(new UpdateBackgroundMessage(new SolidColorBrush(Colors.Blue)));
And you'd register for this message in your MainViewModel's constructor:
Messenger.Default.Register<UpdateBackgroundMessage>(this, message =>
{
Background = message.Brush;
});
Actual message class would be:
public class UpdateBackgroundMessage : MessageBase
{
public UpdateBackgroundMessage(Brush brush)
{
Brush = brush;
}
public Brush Brush { get; set; }
}
I know I'm simplifying things here but I hope you got the idea. Both approaches are valid even if you decide not to use MVVMLight.
Edit:
Here's Git repo with example https://github.com/mikkoviitala/cross-viewmodel-communication
I think you should use Application Properties for storing background. There are various benefit of this :
1) Globally available
2) Easy to remember or store user preference
3) Automatically maintain separate profile for each user as it store values in AppData folder of user.
you can use Messenger to notify that background property has changed so that main window or shell could pull out new background value and update it.

g.i.cs class keeps changing its base class back to its default

g.i.cs class keeps changing its base class back to its default
System.Windows.Controls.Page
After I've changed it to
System.Windows.Controls.UserControl
I need it to be a UserControl because the other partial class' base class is a UserControl
After compiling I get the message
"...file.g.i.cs This file has been modified outside of the source
editor. Do you want to reload it?"
If I say no, I can compile, but after a while, it switches back again. If I choose yes, it instantly switches back.
Any ideas?
Oh, and no one else is working on the files :-)
Look at the beginning of your XAML. You will see a tag that opens with ... change that to and then do the same for the closing tag at the bottom.
You may also have to update your code behind file to make sure the class it defines isn't explicitly derived from the wrong thing.
The file is constantly changing because it is a generated file. this indicates the g in the name. To change a WPF view's base class you have to change it either in the code behind file as well as in WPF itself.
To change it in WPF, you can use the Class attribute:
x:Class="Name.Space.ClassName"
Ok, I didn't find the solution, but I found out why it happens in the first place. When adding the Silver Light file, I chose 'Silverlight Page'. I should have chosen 'Silverlight User Control'. I guess once you choose one, you can't turn it into the other... Hope this helps someone eventually lol.
If you're working on a WPF project, then you need to change window attributes in the XAML file.. in my case I had to change the x:class and xmlns:local.
good luck! :)

How to Find Children of a UserControl instead of a Window - replacing Window.FindName

I currently have a WPF project which has one main Window, and many UserControls which are children of this Window. Many of the children of this window are Tabs. I have successfully replaced my main Window with a User Control that implements almost exactly the same functionality as the Main Window.
Replacing the Window with a UserControl introduced one problem - currently our application determines which programming tab to display based on the parent window by using the Window.FindName method shown below. Therefore I need to replace the Application.Current.MainWindow with an appropriate description of my main user control. See the erroring C# method below and wpf instantiation of the tabs for clarification.
Note Regarding Window.FindName() method - the reason why it does not work after I replaced it with a UserControl is because the FindName method searches upwards in the visual tree, as described here.
Does anyone know how to find a user control based on the x:Name, similar to Application.Current.MainWindow ? Also, is there a better way to find the UserControl than looking for the x:Name string in case it gets renamed?
How we currently find the MainWindow - need to now find MainUserControl:
(C#)
private static void SetCurrentProgram(int num)
{
Window window = Application.Current.MainWindow;
ProgrammingTab programmingTab1 = window.FindName("ProgrammingTab1") as ProgrammingTab;
ProgrammingTab programmingTab2 = window.FindName("ProgrammingTab2") as ProgrammingTab;
programmingTab1.Visibility = num == 1 ? Visibility.Visible : Visibility.Collapsed;
programmingTab2.Visibility = num == 2 ? Visibility.Visible : Visibility.Collapsed;
}
Instantiation of programming tabs.
(xaml)
<Grid>
<ProgrammingControl:ProgrammingTab x:Name="ProgrammingTab1" Program="1" IsVisibleChanged="ProgrammingTab_IsVisibleChanged" />
<ProgrammingControl:ProgrammingTab x:Name="ProgrammingTab2" Program="2" IsVisibleChanged="ProgrammingTab_IsVisibleChanged" />
</Grid>
It sounds like your app is developed in a very WinForms-like style. To stick with that style and simply answer your question, you can FindName() to find the UserControl and again to find the ProgrammingTab, like this:
var userControl = (MyUserControl)Application.Current.MainWindow.FindName("userControlName");
var programmingTab1 = (ProgrammingTab)userControl.FindName("ProgrammingTab1");
var programmingTab2 = (ProgrammingTab)userControl.FindName("ProgrammingTab2");
...
However I would recommend you look into using WPF's advanced capabilities such as data binding. You can have a DependencyProperty "CurrentProgram" on a singleton object referenced by a static property, and simply databind Visiblity to it using a converter.
<ProgrammingTab Visibilty="{Binding CurrentProgram,
Source={x:Static MyState.Instance},
Converter={x:Static VisibleIfEqualConverter},
ConverterParameter=1}" ...>
...
With this change, your SetCurrentProgram becomes simply:
public void SetCurrentProgram(int num)
{
MyState.Instance.CurrentProgram = num;
}
The beauty of this technique is that any ProgrammingTab anywhere in your application will automatically appear or disappear every time MyState.Instance.CurrentProgram's vaulue changes, with no need to find them with FindName() or otherwise.
I figured out a workaround to this problem: I created a new algorithm based on another StackOverflow user's algorithm that recursively found any children of a DependencyObject. Find my solution here. If you declare the FindChild() method in my other post within public static class UIHelper {} you can then solve the problem by doing this:
ProgrammingTab programmingTab1 = UIHelper.FindChild<ProgrammingTab>(Application.Current.MainWindow, "ProgrammingTab1");
ProgrammingTab programmingTab2 = UIHelper.FindChild<ProgrammingTab>(Application.Current.MainWindow, "ProgrammingTab2");
This still uses procedural code instead of declarative XAML for bindings like RayBurns suggested. If it works, his solution will be much more efficient as it wont be traversing a whole tree but rather just changing the visibility of tabs based on a converter. I'll test that solution now and see how it turns out.
The reason why FindName() doesn't work properly is described in the post here.
This article may helps you : http://blog.lexique-du-net.com/index.php?post/2010/09/14/UserControl/Control-how-to-get-a-reference-to-an-element-of-the-template

.Net C# Design View errors

I have subclassed a Treeview and on instantiation it loads a new ImageList (and the associated Images).
Whenever I switch to the designer view, it's also trying to run this code, however the images aren't in the designer's path, so it crashes. I ended up putting in a hack to see if the current directory is "Visual Studio", then do nothing... but that's so ugly.
I find this happening for other things. If a control is trying to use objects during load/initalization that are only available while the program is running, then the Design View cannot bring up the control.
But is there a way to get around this?
I guess what I'm hoping for is having a try/catch for the Designer (only) with the ability to ignore a few errors I know will be happening (like FileNotFoundException, etc.).
Thanks
Everything that inherits from System.Windows.Forms.Control has a DesignMode property that returns a boolean indicating if you are in design mode or not. You could use this to determine when to/when not to load external resources.
Usually it is better to move the loading of these resources to an override of OnLoad as they are rarely required directly at construction. This fixes the issue you are seeing and means that only trees which get displayed at least once will perform these additional resource loading steps.
Otherwise, you can just exclude these steps during design time by checking the DesignMode property and acting accordingly.
This is a fine pattern to use if you're making a control library with a sample of images when shown in the designer or hook ins to other designer features but as a pattern for development I'm not sure it's very effective.
I would suggest shifting your "business logic" (in this case your loading of certain images into a treeview) outside of the bounds of your treeview control. In your case I would place the logic within the Load event of the form that the control is inside:
public void Load(object sender, EventArgs e)
{
string path = "c:\somePath\toAwesome\Images";
myFunkyTreeView.AddImages(path);
}
For larger apps I personally think you want to shift the logic even out of the forms themselves, but this is debatable measure as it requires additional plumbing as a trade-off for the flexibility this provides.
Thanks for pointing me in the right directioon guys.
I had tried registering to the OnLoad event, but that event is triggered when the Design View comes up, so that didn't quite work for me (am I doing something wrong?).
Anyway, I looked a bit more into the DesignMode property. It can only work for Controls, and sometimes your object may not even be a control.
So here's the answer I prefer:
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime) {
// design-time stuff
} else {
// run-time stuff
}
Found it here.

Categories