1

I'm trying to do a recent upload system so I have last 5 (or less) upload listed in a contextMenu.

Problem is when I was doing the static test version (not dynamic) everything was fine. Like can show this screenshot :

Everything was fine

And now with my dynamic version :

<ItemsControl ItemsSource="{Binding Uploads}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel></StackPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <MenuItem IsCheckable="False" ItemsSource="{Binding}">
                    <MenuItem.Icon>
                        <Image Source="Resources/upload.ico"></Image>
                    </MenuItem.Icon>
                    <MenuItem.Header>
                        <TextBlock Text="{Binding name}"></TextBlock>
                    </MenuItem.Header>
                    <DataTemplate>
                        <StackPanel>
                            <MenuItem Command="{Binding GoToUrl}" CommandParameter="{Binding url}">
                                <MenuItem.Header>
                                    <Image IsEnabled="False" HorizontalAlignment="Center" Source="{Binding miniature}"></Image>
                                </MenuItem.Header>
                            </MenuItem>
                            <Separator/>
                            <MenuItem IsEnabled="False" IsCheckable="False">
                                <MenuItem.Header>
                                    <StackPanel IsEnabled="False">
                                        <TextBlock IsEnabled="False" Text="{Binding uploadDateText}"></TextBlock>
                                        <TextBlock IsEnabled="False"  Text="{Binding viewNumberText}"></TextBlock>
                                    </StackPanel>
                                </MenuItem.Header>
                            </MenuItem>
                            <Separator/>
                            <MenuItem Header="Copy To Clipboard" Command="{Binding CopyToClip}" CommandParameter="{Binding url}" />
                            <MenuItem Header="Open in browser" Command="{Binding GoToUrl}" CommandParameter="{Binding url}" />
                            <MenuItem Header="Delete" Command="{Binding Delete}" CommandParameter="{Binding}" />
                        </StackPanel>
                    </DataTemplate>
                </MenuItem>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

I got this

The problem is when you have a menuItem inside another menuItem, there is the little arrow but since I have wrapped all with the ItemsControl, I can't access submenuItem and I have a strange selection bug.

I've seen in this question an information about why it is not working as expected. Due to the fact that the itemsControl is like a menuItem. But I didn't find a way to do it another way.

My goal is a "foreach loop" of MenuItem with other menuItem in it. Maybe i don't have a good approach.

P.S: Sorry for my bad english, i'm French.

Edit 1:

It appears I wasn't clear enough so i add some more information.

Goal : Having up to 5 menu foreach upload a client has done. If he has only done 3 then 3 Menu will be in the context Menu. Foreach Menu the header will be the name of the file he uploaded. If the user want to have more information on the upload, he will mouseover the upload name and then a submenu will draw. This submenu will show all the information about the upload : image, size, date when uploaded, etc...

How I did it : I created a class that will receive from a web API all information needed : Upload

public class Upload
{
    public string uniqueId { get; set; }
    public DateTime uploadedDateTime { get; set; }
    public int _viewNumber { get; set; }
    public string url { get; set; }
    public string minUrl { get; set; }
    public string type { get; set; }
    public int size { get; set; }
    public string name { get; set; }

    public BitmapImage miniature
    {
        get
        {
            BitmapImage bitmap = new BitmapImage();
            bitmap.BeginInit();
            bitmap.UriSource = new Uri(this.minUrl, UriKind.Absolute);
            bitmap.EndInit();

            return bitmap;
        }
    }

    public string uploadDateText
    {
        get
        {
            return "Uploaded : " + this.uploadedDateTime.Date.ToString() + " at " + this.uploadedDateTime.TimeOfDay.ToString();
        }
    }

    public string viewNumberText
    {
        get
        {
            return "Number of views : " + this._viewNumber;
        }
    }

    public Upload(string p_uniqueId, DateTime p_uploadedDateTime, int p_viewNumber, string p_url, string p_minUrl, string p_type, int p_size, string p_name)
    {
        uniqueId = p_uniqueId;
        uploadedDateTime = p_uploadedDateTime;
        _viewNumber = p_viewNumber;
        url = p_url;
        minUrl = p_minUrl;
        type = p_type;
        size = p_size;
        name = p_name;
    }

    public void delete()
    {
        MessageBox.Show("Deleting item with id : " + this.uniqueId);
    }

    public void refresh()
    {

    }
}

So when i will ask my WebApp the last 5 upload of my user, i will have a List.

Containing up to 5 Items

To make this list available for my contextMenu i converted it in an ObservableCollection with this code :

public ObservableCollection<Upload> Uploads
    {
        get
        {
            App.GetAppRef().initUploads();
            return App.GetAppRef().Uploads;
        }

    }

I've spent a lot of time to find a way to have a variable number of menuItem in my context menu. Indeed there can be 2 menuItem if the user has only uploaded 2 files. It's a FIFO queue style (First In First Out) with a maximum of 5 element.

So i look at a "foreach" equivalent in xaml. But I only found a way to do a "xaml foreach" with a ItemsControl with his itemSource binding to my ObservableCollection of Upload.

If anyone has a better idea to do this, please tell me because I know it may not be the best solution.

Anyway, I have other menuItem before and after this "last 5 upload" section in my context menu so i have to do my foreach inside a specific area of the context menu.

What is the problem : For now, i cannot go on my subitemMenu and i have the selection that is enabled for my itemControls so i need to find a better way to achieve this.

I hope I have clarified a bit more my problem.

Community
  • 1
  • 1
  • Not sure I understand what you are trying to achieve. By dynamic do you mean that you want to define a certain menu template once that can be added to other menu items as a submenu? – o_weisman Feb 09 '16 at 07:40
  • Well my webApi will give me the last image uploaded by the user, i want my user to be able to get the last 5 image they uploaded on their desktop client. So i have an array of 5 object and i need foreach one a menuitem with his name and if you let your cursor on it, it will show more details like in the screenshot i posted. – Sebastien Lemichez Feb 09 '16 at 07:49
  • I think I see what you mean. This question seems to deal with the dynamic / static combination in a menu which you need: http://stackoverflow.com/questions/14489112/how-do-i-dynamically-bind-and-statically-add-menuitems . As for adding a submenu to each dynamic menu item, try adding it in the part of the answer given in that question. – o_weisman Feb 09 '16 at 07:52
  • thx gonna look at this – Sebastien Lemichez Feb 09 '16 at 07:59
  • Without a good [mcve] that reliably reproduces the problem, it's not practical for anyone to try to solve your specific problem. Do note, however, that `MenuItem` already _is_ an `ItemsControl`; it's not clear why you are "wrapping" the menu in a bare `ItemsControl` element. If you improve the question, be sure you explain why you've done that rather than just using `MenuItem` directly. – Peter Duniho Feb 09 '16 at 08:06
  • I can't really adapt what you sent me because the itemContainerStyle apply for every item (static or dynamic) but in my case i have other menu that are not related to the recent upload. It would have work if all my dynamic menuItem were in a separate tree level. – Sebastien Lemichez Feb 09 '16 at 08:27
  • Maybe you can add a DataTemplate for your DataType as shown here: http://stackoverflow.com/questions/5473001/itemscontrol-with-multiple-datatemplates-for-a-viewmodel – o_weisman Feb 09 '16 at 13:46

1 Answers1

0

Ok, took me some time to get to it but here is an answer that works (I combined it from several answers found on stackoverflow).
The Xaml:

<local:MyStyleSelector x:Key="MyStyleSelector" />

<Style x:Key="DynamicItemStyle" TargetType="MenuItem">
    <Setter Property="Header" Value="{Binding Path=Title}"/>
    <Setter Property="ItemsSource">
        <Setter.Value>
            <x:Array Type="FrameworkElement">
                <MenuItem Header="Active" IsCheckable="True" IsChecked="{Binding Path=Active}"></MenuItem>
                <Separator></Separator>
                <MenuItem Header="Edit" Name="EditCustomMessageButton" Style="{x:Null}"></MenuItem>
                <MenuItem Header="Delete" Name="DeleteCustomMessageButton" Style="{x:Null}"></MenuItem>
            </x:Array>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="StaticItemStyle" TargetType="MenuItem">
</Style>

And in the code:

public class MyStyleSelector : StyleSelector
    {
        public override Style SelectStyle(object item, DependencyObject container)
        {
            var itemsControl = ItemsControl.ItemsControlFromItemContainer(container);
            if (item is MyObject)
                return (Style)itemsControl.FindResource("DynamicItemStyle");
            else 
                return (Style)itemsControl.FindResource("StaticItemStyle");
        }
    }
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            MyObject dynamicObject = new MyObject();
            dynamicObject.Title = "dynamic1";
            Windows.Add(dynamicObject);
            Windows.Add(new MyObject() { Title = "dynamic2" });
        }

        private ObservableCollection<MyObject> _windows = new ObservableCollection<MyObject>();


        public ObservableCollection<MyObject> Windows
        {
            get { return _windows; }
            set { _windows = value; }
        }
    }

    public class MyObject
    {
        public string Title { get; set; }
    }

`

o_weisman
  • 804
  • 7
  • 19
  • I've been using another way to achieve my goal but anyway your code do the job with some ajustements nice work. I hope it will help other people as well. – Sebastien Lemichez Feb 25 '16 at 17:18