How to refresh xmlDataProvider when xml document changes at runtime in WPF? - c#

I am trying to make a image viewer/album creator in visual studio, wpf. The image paths for each album is stored in an xml document which i bind to to show the images from each album in a listbox.
The problem is when i add a image or an album at runtime and write it to the xml document. I can't seem to make the bindings to the xml document update so they show the new images and albums aswell.
Calling Refresh() on the XmlDataProvider doesn't change anything.
I don't wish to redo the binding of the XmlDataProvider, just make it read from the same source again.
XAML:
...
<Grid.DataContext>
<XmlDataProvider x:Name="Images" Source="Data/images.xml" XPath="/albums/album[#name='no album']/image" />
</Grid.DataContext>
...
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Bottom" Padding="0" Margin="0,0,0,5" Content="{x:Static resx:Resource.AddImageLabel}"/>
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Name="newImagePath" Margin="0" />
<Button Grid.Row="1" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Bottom" Name="newImagePathButton" Content="{x:Static resx:Resource.BrowseImageButton}" Click="newImagePathButton_Click" />
...
<ListBox Grid.Column="0" Grid.ColumnSpan="4" Grid.Row="3" HorizontalAlignment="Stretch" Name="thumbnailList" VerticalAlignment="Bottom" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding BindingGroupName=Images}" SelectedIndex="0" Background="#FFE0E0E0" Height="110">
...
Code behind:
private void newImagePathButton_Click(object sender, RoutedEventArgs e)
{
string imagePath = newImagePath.Text;
albumCreator.addImage(imagePath, null);
//Reset import image elements to default
newImagePath.Text = "";
//Refresh thumbnail listbox
Images.Refresh();
Console.WriteLine("Image added!");
}
public void addImage(string source, XmlElement parent)
{
if (parent == null)
{
//Use default album
parent = (XmlElement)root.FirstChild;
}
//Create image element with source element within
XmlElement newImage = xmlDoc.CreateElement(null, "image", null);
XmlElement newSource = xmlDoc.CreateElement(null, "source", null);
newSource.InnerText = source;
newImage.AppendChild(newSource);
//Add image element to parent
parent.AppendChild(newImage);
xmlDoc.Save(xmlFile);
}
Thank you very much for any help!

The right way in this situation I beleive is to use ObservableCollection and bind it to ItemsSource property of your ListView. So, just play with objects and no tricks with XML files.
Edit:
Entire concept is work with Refresh(). Next sample is works. Check if Refresh() call is made after document saving.
<ListView x:Name="uiList" ItemsSource="{Binding}">
<ListView.DataContext>
<XmlDataProvider x:Name="DataSource" Source="c:\XMLFile.xml" XPath="/root/item" />
</ListView.DataContext>
<ListView.ItemTemplate>
<DataTemplate>
<Border Width="40" Height="40" Background="Gray">
<Label Content="{Binding Attributes[0]}" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
...
public MainWindow()
{
InitializeComponent();
uiList.SelectionChanged += new SelectionChangedEventHandler(uiList_SelectionChanged);
}
void uiList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string sFile = #"c:\XMLFile.xml";
XDocument oDoc = XDocument.Load(sFile);
oDoc.Root.Add(
new XElement("item", new XAttribute("name", "test3"))
);
oDoc.Save(sFile);
XmlDataProvider oProv = uiList.DataContext as XmlDataProvider;
oProv.Refresh();
}

Possible Problem and Solution #1
Do you have this XmlDataProvider resource declared in the Application.Resources block as well?
If you do, the XAML UI ListBox element "thumbnailList" refers to the Grid panel's instance of the XmlDataProvider. I'm guessing, since I can't see the code in your Window CS file constructor, that you refer to the Application-level instance of the XmlDataProvider when you address the XmlDataProvider there as in
XmlDataProvider xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;
XmlDocument xDoc = xmlDataProvider.Document;
If this is the case, remove the XmlDataProvider resource from the Grid element. Now when your code-behind updates the XML file the UI will automatically update.
Possible Problem and Solution #2
I see from your addImage() method that you refer to an instance variable named "xDoc".
The other possibility is that you are creating a NEW XmlDocument in your Window constructor, instead of referencing the XAML created XmlDocument object. If so, get the instance of the current XmlDocument instead of creating a new instance. Make sure to declare the resource
at the Application level and remove the resource declaration from the Grid element
XmlDataProvider xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;
Or reference the resource at the Grid element (you will need to add a Name to the Grid) and do not declare the resource in the Application.Resources block
XmlDataProvider xmlDataProvider = grid.FindResource("Images") as XmlDataProvider;
XmlDocument xDoc = xmlDataProvider.Document;
Now when your code-behind updates the XML file the UI will automatically update.
Conclusions
If you declare these two class instance variables in your code-behind
XmlDataProvider xmlDataProvider;
XmlDataProvider gridXmlDataProvider;
and have this code in your Window constructor
xmlDataProvider = Application.Current.FindResource("Images") as XmlDataProvider;
gridXmlDataProvider = grid.FindResource("Images") as XmlDataProvider;
Put a stop in the addImage event handler right you add a Node and save the XML Document changes. Assuming you originally loaded oDoc from xmlDataProvider as shown above. Run in Debug Mode and open a Watch window and inspect the contents of xmlDataProvider and gridXmlDataProvider. Open the Document property on each, and compare the contents of the InnerXml property. On the xmlDataProvider (the Application-level's resource) you will find
the latest node changes to the XML file are reflected. Not so on the gridXmlDataProvider (the XAML UI element's resource). InnerXml property shows no changes. No changes, not need to update the UI.
FYI I had Problem #1 - the same XmlDataProvider resource declared in the Application.Resources block AND in the Window.Resources block. I started out with the latter declaration, ran into an exception error after I referred to the XmlDataProvider instance via Application.Current.FindResource("name"), copy and pasted the declaration into the Application.Resources block, LEAVING the resource declared in the Window.Resources block, creating a TWO REFERENCE problem. The XAML UI used the Window data context, while my code-behind updated the XML file with the Application data context! Whenever I added or removed nodes from the XML file the UI (ListBox) did not get updated!
BTW XmlDataProvider already implements its own notification mechanism, no need use an ObservableCollection. oProv.Refresh() does not cause the refresh of the bound UI because it may point to a different instance of the XmlDataProvider (the Grid element's), and as far as that instance is concerned, no changes have happened.
This answer probably comes too late for you, but I just found this stuff out, thought I share it.

in xml
<XmlDataProvider Source="XMLFile1.xml" XPath="Data" DataChanged="XmlDataProvider_DataChanged"></XmlDataProvider>
</Window.DataContext>
in cs
private void XmlDataProvider_DataChanged(object sender, EventArgs e)
{
Dispatcher.BeginInvoke((Action)(() =>
{
XmlDataProvider oProv = this.DataContext as XmlDataProvider;
oProv.Refresh();
}));
}

Taken from..http://www.infosysblogs.com/microsoft/2008/03/wpf_updating_xmldataprovider_w.html
Ive used below;
XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"] as XmlDataProvider;
xdp.Source = new Uri(MyPath + #"\Projects.xml");
FileSystemWatcher watcher = new FileSystemWatcher();
//set the path of the XML file appropriately as per your requirements
watcher.Path = MyPath;
//name of the file i am watching
watcher.Filter = "Projects.xml";
//watch for file changed events so that we can refresh the data provider
watcher.Changed += new FileSystemEventHandler(file_Changed);
//finally, don't forget to enable watching, else the events won't fire
watcher.EnableRaisingEvents = true;
and
void file_Changed(object sender, FileSystemEventArgs e)
{
XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"] as XmlDataProvider;
xdp.Refresh();
}
and in my UserControl;
<UserControl.Resources>
<XmlDataProvider x:Key="userDataXmlDataProvider1" XPath="Projects/Project" IsAsynchronous="True" />
<CollectionViewSource x:Key="userDataCollectionViewSource1" Source="{StaticResource userDataXmlDataProvider1}"/>
</UserControl.Resources>
<Grid DataContext="{StaticResource userDataXmlDataProvider1}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<ListBox x:Name="listBox1" Grid.Row="1"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8,0,8,0">
<Label Content="{Binding XPath=ProjectName}" Width="100" Margin="5" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>

Related

Unable to access UserControl using X:Name from XAML Page

This is my user control embedded into my page list view
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:ZTask">
<local:AddUserControl x:Name="MyUserControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</DataTemplate>
</ListView.ItemTemplate>
I have Named it as MyUserControl using X: Name. I tried accessing this in my page cs file but it gives me an error saying "The name 'MyUserControl' does not exist in the current context".
Help me fix this issue.
Which particular instance of AddUserControl are you expecting to get a reference to since there will be an AddUserControl added per item in the ListView?
If you want to do something with the AddUserControls, you could handle the Loaded event for each one of them:
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:ZTask">
<local:AddUserControl x:Name="MyUserControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Loaded="OnLoaded"/>
</DataTemplate>
</ListView.ItemTemplate>
private void OnLoaded(object sender, RoutedEventArgs e)
{
AddUserControl auc = (AddUserControl)sender;
//...
}

How do I Access Items of a Listbox

I have a Listbox done like this
<ListBox x:Name="lbAlbumSelect">
<ListBox.ItemTemplate>
<DataTemplate>
<Button>
<Button.Content>
<StackPanel>
<Image />
<TextBlock TextWrapping="Wrap"
Text="{Binding album_name}" />
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to access every Image programmatically and set its Source. I tried to navigate the listbox like this
foreach (Button btn in lbAlbumSelect.Items)
{
StackPanel stack=btn.Content;
Image image=stack.Children.ElementAt(0) as Image;
//every ListBoxItem is binded to a clsAlbums object that contains various data,
//also the name of the image file, but not the path.
string pathImg = #"/Assets/Images/" + (btn.DataContext as clsAlbums).album_img;
LoadImage(pathImg, image); //function that sets image source to path img
}
But gives me a Invalid Cast Exception on the foreach clause.
Is there a faster and more correct way to do this?
You should ideally be binding the image source to the control. Add an additional property to your class clsAlbums which can be bound to the Image source.
public class clsAlbum
{
public string album_name { get; set; }
public string album_img { get; set; }
public string album_img_src
{
get
{
return #"/Assets/Images/" + album_img;
}
}
}
Now bind this additional property album_img_src to your xaml.
<ListBox x:Name="lbAlbumSelect">
<ListBox.ItemTemplate>
<DataTemplate>
<Button>
<Button.Content>
<StackPanel>
<Image Source="{Binding album_img_src}"/>
<TextBlock TextWrapping="Wrap"
Text="{Binding album_name}" />
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The item is actually ListBoxItem. In your case Button is content of ListBoxItem and Content of Button is StackPanel and Image is child of StackPanel. So you need to traverse visual tree somehow and you can do so using Linq to visual tree, for example. http://www.codeproject.com/Articles/63157/LINQ-to-Visual-Tree
Probably easiest way of accessing elements inside datatemplate is from it's loaded or initialized event:
here:
<Image Loaded="Image_Loaded" />
void Image_Loaded(object sender, EventArgs e){
var image =(Image)sender;
Try to avoid acessing elements inside datatemplates. 90% times you achieve your goal better, using ViewModel, converters, using behaviours, datatriggers or extracting datatemplate to separate UserControl

xmldataprovider using element values in code behind

I have a wpf window where I am using xml data through an XMLDataProvider. The screen is based on a grid and all the data is being displayed correctly, having defined the xml as follows...
<Grid.DataContext>
<XmlDataProvider x:Name="Data" XPath="Book/Page" />
</Grid.DataContext>
With the xml source being set in code behind as follows...
InitializeComponent();
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
Data.Source = new Uri(appPath + #"\SNG.xml");
All so good so far. But now I have a need to read one of the elements from the xml file in the code behind. All my searching and the only way I've found to do it is to bind it to an invisible control then read the data out of the control. e.g. to read the BookRef from the xml I have the following in the xaml...
TextBlock Name="BookRefTextBox" Visibility="Hidden" Text="{Binding XPath=#BookRef}"/>
Then in the code behind...
string bookRef = BookRefTextBox.Text;
This works, I can then use the data that came from the xml file... but it really feels like a fudge. Is there a better way to get the value of parts of the xml file from within the code behind section.
EDIT:
Forgot to say that I've also tried putting the XmlDataProvider in Windows.Resources instead of in Grid.DataContext as some examples I've found do.
However I then can't find a way to set the path to the xml file in code behind. Added to which putting it in Windows.Resource does not make it any easier to find how to access the data from the Xml file.
EDIT2:
Here is an example of the XML file. Note there are multiple books.
<Books>
<Book Id="1" BookRef="12345" Name="My Book Name" Author="Author" Pages="2" >
<Page PageId="1"/>
<Page PageId="2"/>
</Book>
<Book Id="1" BookRef="67890" Name="My Second Book Name" Author="Author 2" Pages="1" >
<Page PageId="1"/>
</Book>
</Books>
OK, here is another way, more complicated, though.
You are correct that you need to synchronize the currently displayed page with the text of the BookRefTextBox. So on top of XmlDataProvider, I define a CollectionViewSource, which can be used to keep track of the current displayed page.
In the XAML code below, both BookRefTextBox and listBox1 (I use ListBox to display pages) are bound to the same CollectionViewSource, and by setting IsSynchronizedWithCurrentItem="True", the current item is updated when the selected page is changed.
The interesting point is the XPath expression for BookRefTextBox.Test, XPath=../#BookRef means the Text of BookRefTextBox is Find the parent element of the current page- which is Book, and display its BookRef attribute".
The whole XAML of the window.
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<XmlDataProvider x:Key="userDataXmlDataProvider1" Source="/Data/XMLFile1.xml" XPath="Books/Book/Page"/>
<CollectionViewSource x:Key="userDataCollectionViewSource1" Source="{StaticResource userDataXmlDataProvider1}"/>
</Window.Resources>
<Grid DataContext="{StaticResource userDataXmlDataProvider1}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="BookRefTextBox" Grid.Row="0" Text="{Binding XPath=../#BookRef}" />
<ListBox x:Name="listBox1" Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8,0,8,0">
<Label Content="{Binding XPath=#PageId}" Width="100" Margin="5" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And from code behind, you can get the text of the BookRefTextBox.
Edit:
In order to set the source from code behind:
If XmlDataProvider is declared in Window.Resources, it has a x:Key attribute, you access a resource via Key, not Name.
XmlDataProvider xdp = this.Resources["userDataXmlDataProvider1"] as XmlDataProvider;
xdp.Source = new Uri(...);
I believe I have finally found the answer that avoids the use of a hidden control. First off many thanks to kennyzx for his answer which while it still used a hidden control was invaluable in leading me to this answer.
Instead of putting the XmlDataProvider in the Grid.Context it has been moved to the Window.Resources and a CollectionViewSource added to accompany it.
<Window.Resources>
<XmlDataProvider x:Name="books" x:Key="bookData" XPath="Books/Book/Page"/>
<CollectionViewSource x:Key="collBookData" Source="{StaticResource bookData}"/>
</Window.Resources>
A new XmlDataProvider is defined in the code behind and in the constructor of the window is set to be a reference to the one defined in the XAML Windows.Resources.
XmlDataProvider bookData;
public BookPage()
{
InitializeComponent();
string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
bookData = (XmlDataProvider)this.Resources["bookData"];
bookData.Source = new Uri(appPath + #"\SNG.xml");
}
The DataContext of the Grid is set to be the CollectionViewSource.
<Grid.DataContext>
<Binding Source="{StaticResource collBookData}"/>
</Grid.DataContext>
The above is not 100% necessary as it could be specified on each control instead, but this way makes for simpler binding on each control on the form. (No hidden controls in this solution, only the ones I want to actually show). For example...
<TextBlock Name="myTextBlockName" Style="{StaticResource MyTextBlockStyle}" Text="{Binding XPath=../#BookRef}" />
Finally the bit to read the data from the XML in code behind.
XmlNode currentXmlNode = bookData.Document.SelectNodes("Books/Book/Page").Item(collBookData.View.CurrentPosition);
string currentBookRef = currentXmlNode.ParentNode.Attributes["BookRef"].Value;
Just as an aside, this solution also allows me to use MoveCurrentToPrevious and MoveCurrentToNext against collBookData.View to change the current page being displayed (previously had a hidden listbox control to do that and wasn't happy with that solution either).

Xaml TextBox not behaving

I am trying to create UI from XAML at runtime, with something like
private void Application_Startup (object esender, StartupEventArgs e)
{
this.RootVisual = (UIElement)(XmlReader.Load(e.InitParams["Xaml"])
If I feed it this XAML:
<Canvas
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
<StackPanel>
<TextBox Width="120" Margin="8" Text="Edit Me" />
<CheckBox Margin="8">Normal</CheckBox>
<ComboBox Margin="8" Width="120" SelectedIndex="1">
<ComboBoxItem Content="First Normal Item" />
<ComboBoxItem Content="Second Normal Item" />
</ComboBox>
</StackPanel>
</Canvas>
then the check box and list behave as expected, but my TextBox does not respond to typing, it stays with its initial value.
The really weird (to me) part is that if I put a handler for KeyDown on to the RootVisual, and in there display a message box, it works. If I have an empty handler or no handler it doesn't.
Do I need to set up some explicit handling for some events? If so, which ones, and how do I handle them?
Upadate: as suggested, I tried putting the dynamic markup into the MainPage of a new app, like this:
public MainPage()
{
InitializeComponent();
var dynamic = XamlReader.Load(xaml);
this.LayoutRoot.Children.Add(dynamic as UIElement);
}
where xaml is a string literal containing the content as above, and everything else is just how VS2010 wizard left it. That works. But I can't see what the effective difference is.
Update update: that's a red herring; the difference is the environment. It works in VS, but not in the Silverlight ActiveX control that I am using in the real app.
Did you define the root namespace on your root element?
<param name="initParams" value="xaml=<TextBox xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' Text='hi'/>" />
Just a shot in the dark here, but have you tried adding the dynamically created content as the child of a static "MainPage.xaml" Grid instead of as RootVisual?
Check for IsEnabled="True" property in your main XAML file, if it is set to false then controls will not be editable.

Ribbon ApplicationMenu AuxilaryPane Size

How do I change the size of the AuxilaryPane in the WPF Ribbon ApplicationMenu? I have added a recent file list to that area but it is getting truncated. Ideally I'd like the auxilary pane to fill the screen like it does for Word/Excel.
My code:
<r:Ribbon.ApplicationMenu>
<r:RibbonApplicationMenu>
<r:RibbonApplicationMenu.AuxiliaryPaneContent>
<StackPanel>
<TextBlock Text="Recent Files" />
<Separator />
<ItemsControl ItemsSource="{Binding RecentFiles}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<r:RibbonApplicationMenuItem Header="{Binding ShortPath}"
Command="{Binding DataContext.OpenRecentFileCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding LongPath}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</r:RibbonApplicationMenu.AuxiliaryPaneContent>
</r:RibbonApplicationMenu>
</r:Ribbon.ApplicationMenu>
Based on the answers in this thread I found it easiest to subclass RibbonApplicationMenu and set Width of the third column to Auto.
public class CustomRibbonApplicationMenu : System.Windows.Controls.Ribbon.RibbonApplicationMenu
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
System.Windows.DependencyObject obj = this.GetTemplateChild("PART_AuxiliaryPaneContentPresenter");
System.Windows.Controls.ContentPresenter c = obj as System.Windows.Controls.ContentPresenter;
((System.Windows.Controls.Grid)((System.Windows.Controls.Border)c.Parent).Parent).ColumnDefinitions[2].Width = System.Windows.GridLength.Auto;
}
}
Now you just need to change your Ribbon xaml from
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu>
to
<Ribbon.ApplicationMenu>
<ctrl:CustomRibbonApplicationMenu>
If you are looking for a very quick fix to increase the height, you can simply add some useless RibbonApplicationMenuItems to pad out the box (and not have to modify MS source code).
<ribbon:Ribbon.ApplicationMenu>
<ribbon:RibbonApplicationMenu>
<ribbon:RibbonApplicationMenu.Items>
<ribbon:RibbonApplicationMenuItem Name="saveSettings" Header="Save Settings" />
<ribbon:RibbonApplicationMenuItem IsEnabled="False"/> <!--USELESS-->
<ribbon:RibbonApplicationMenuItem IsEnabled="False"/> <!--USELESS-->
</ribbon:RibbonApplicationMenu.Items>
<ribbon:RibbonApplicationMenu.AuxiliaryPaneContent >
<StackPanel Orientation="Vertical" >
<GroupBox>
<Label Content="System Settings" />
</GroupBox>
<StackPanel Orientation="Horizontal">
</StackPanel>
</StackPanel>
</ribbon:RibbonApplicationMenu.AuxiliaryPaneContent>
</ribbon:RibbonApplicationMenu>
</ribbon:Ribbon.ApplicationMenu>
I've searched a solution for the same problem.
There is no direct property to modify this.
An Example of creating such property can be found at
msdn
here's the main solution:
Change the source code of the Ribbon Library. MS has provided the source code of the Ribbon Library: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=2bfc3187-74aa-4154-a670-76ef8bc2a0b4
Download the source code and open it, in the MicrosoftRibbonForWPFSourceAndSamples\RibbonControlsLibrary\Microsoft\Windows\Controls\Ribbon\RibbonApplicationMenu.cs, add one Dependency Property:
public double MinMenuHeight
{
get { return (double)GetValue(MinMenuHeightProperty); }
set { SetValue(MinMenuHeightProperty, value); }
}
public static readonly DependencyProperty MinMenuHeightProperty =
DependencyProperty.Register("MinMenuHeight", typeof(double), typeof(RibbonApplicationMenu), new UIPropertyMetadata(0.0));
In the MicrosoftRibbonForWPFSourceAndSamples\RibbonControlsLibrary\Themes\Generic.xaml, line 7519, add the XAML code:
<Border x:Name="PopupBorder" MinHeight="{TemplateBinding MinMenuHeight}" BorderBrush="{Binding RelativeSource={RelativeSource AncestorType={x:Type ribbon:RibbonMenuButton}}, Path=Ribbon.BorderBrush}" Background="{Binding RelativeSource={RelativeSource AncestorType={x:Type ribbon:RibbonMenuButton}}, Path=Ribbon.Background}" BorderThickness="1" CornerRadius="2">
<Grid>
</Grid>
</Border>
Only add the first two rows of the given xaml
This is an old thread, yes, and it has some good ideas, but I wasn't satisfied.
My problem was slightly different in that I needed the ApplicationMenu to expand only enough to fit any control that was placed in the auxiliary pane.
Eventually I dug deep and found a solution I was happy with. It doesn't solve the "fill the screen" problem, but I'm hoping this will help others who land here, looking for a solution to a problem similar to mine. Sorry if it looks like I'm trying to hijack the thread. I don't intend to.
Essentially I solved the fixed width and height problem by changing the ribbon style:
Open the ribbon assembly in JetBrains DotPeek
Open Resources/System.Windows.Controls.Ribbon.g.resources/Themes/generic.baml
Copy the entire resource dictionary into a .xaml file in your project. You may be able to get away with using only a part of it, but I decided to take the whole thing.
At this point you may be asking, "Why not just use VS or Blend or ShowMeTheTemplate instead of DotPeek?" All of these tools failed miserably on the ribbon assembly. Don't know why. They didn't say. One of the problems with using DotPeek is that some of the namespace references will need adjusting, but it's not too difficult, so I won't go into details here.
So, now that you have all the styles and templates, go look for the offending markup.
First, fix the width:
Look for the grid whose third column definition is a static value of 300. You can search for <ColumnDefinition Width="300"/>. There is only one.
Change the "300" to "Auto".
Then fix the height:
Look for the definition of PART_SubmenuPlaceholder Border. You can search for x:Name="PART_SubmenuPlaceholder". It is about 50 lines below the change you did for the width.
That Border binds the Height property to the ActualHeight property of the "MainPaneBorder" control: Height="{Binding ElementName=MainPaneBorder, Path=ActualHeight}".
Remove this Height definition.
Now that you've modified the style, just add this resource dictionary to your xaml and it should apply itself to the ribbon.
When I came across this answer (while searching for an answer to my own, slightly different question), I wasn't too excited about actually modifying Microsoft code.
As a result, I rather preferred to subclass it, and get the hold of the necessary UI element using base.GetTemplateChild on the relevant "PART_...".
I suppose you can follow a similar approach to achieve what you need.
My example is here.
Hope this helps.
P.S. If you happen to find a way to determine the necessary width of the AuxiliaryPanel, please let me know - I would like to see if that's applicable to the menu's width as well.
You can download the Microsoft Ribbon for WPF Source Code (http://www.microsoft.com/en-us/download/details.aspx?id=11877) and add a DependencyProperty Width/Height to ApplicationMenu or just do it 'quick and dirty' like in my example:
MainWindow.xaml
public partial class MainWindow : RibbonWindow
{
private Size DefaultApplicationMenuSize;
public MainWindow()
{
InitializeComponent();
}
private void RibbonWindow_Loaded(object sender, RoutedEventArgs e)
{
var grid = (myRibbon.ApplicationMenu.Template.FindName("MainPaneBorder", myRibbon.ApplicationMenu) as Border).Parent as Grid;
/* before the first opening of the menu the size is NaN, so you have to measure size and use the DesiredSize */
grid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.DefaultApplicationMenuSize = new Size(grid.ColumnDefinitions[2].Width.Value, grid.DesiredSize.Height);
}
private void RibbonApplicationMenuItem_MouseEnter(object sender, MouseEventArgs e)
{
Button b=new Button();
b.Content = "my epic button";
b.Width = 500;
b.Height = 500;
b.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
SetApplicationMenuSize(b.DesiredSize);
this.ribbonContentPresenter.Content = b;
}
private void RibbonApplicationMenuItem_MouseLeave(object sender, MouseEventArgs e)
{
SetApplicationMenuSize(DefaultApplicationMenuSize);
this.ribbonContentPresenter.Content = null;
}
private void SetApplicationMenuSize(Size size)
{
var grid = (myRibbon.ApplicationMenu.Template.FindName("MainPaneBorder", myRibbon.ApplicationMenu) as Border).Parent as Grid;
/* you can modify the width of the whole menu */
//grid.Width = size.Width;
/* or just the size of RibbonApplicationMenu.AuxiliaryPaneContent */
grid.ColumnDefinitions[2].Width = new GridLength(size.Width);
grid.Height = size.Height;
}
}
MainWindow.xaml.cs
<ribbon:RibbonWindow x:Class="WpfRibbonApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
Title="MainWindow"
x:Name="RibbonWindow"
Width="640" Height="480"
Loaded="RibbonWindow_Loaded">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ribbon:Ribbon x:Name="myRibbon">
<ribbon:Ribbon.ApplicationMenu>
<ribbon:RibbonApplicationMenu SmallImageSource="Images\SmallIcon.png">
<ribbon:RibbonApplicationMenuItem Header="Hello _Ribbon"
ImageSource="Images\LargeIcon.png"/>
<ribbon:RibbonApplicationMenuItem Header="HoverTest"
ImageSource="Images\LargeIcon.png"
MouseEnter="RibbonApplicationMenuItem_MouseEnter"
MouseLeave="RibbonApplicationMenuItem_MouseLeave"
StaysOpenOnClick="True" />
<ribbon:RibbonApplicationMenu.FooterPaneContent>
<ribbon:RibbonButton Label="What ever" HorizontalAlignment="Right"/>
</ribbon:RibbonApplicationMenu.FooterPaneContent>
<ribbon:RibbonApplicationMenu.AuxiliaryPaneContent>
<ribbon:RibbonContentPresenter Name="ribbonContentPresenter" />
</ribbon:RibbonApplicationMenu.AuxiliaryPaneContent>
</ribbon:RibbonApplicationMenu>
</ribbon:Ribbon.ApplicationMenu>
</ribbon:Ribbon>
</Grid>
</ribbon:RibbonWindow>
have a nice day

Categories