简体   繁体   中英

WPF MVVM Custom Control issue

I am trying to create a custom Tag Cloud control. The way I would like it to work is that the user can give it a collection of strings in the itemsSource and the converted string will be displayed in the UI.

At the moment what I have is a tag cloud MVVM app which takes a collection strings (containing duplicates) and groups them by “Name” using a collectionView and then a converter is used to decide the FontSize of each string (tag) based on the count of that string.name in the original collection.

So my question is how can I change this to my desired custom control? I know I will have to extend listbox in my code-behind to expose the itemsSource but the part I am confused about is what to do about the grouping logic (collectionView) as I am currently binding to a grouped collection (no duplicates) whereas I want the user to be able to provide any collection. Please let me know if this is clear.?

Grouping logic

public class TagCloudControl : ListBox
{
    public ObservableCollection<TagModel> Tags { get; set; }

    static TagCloudControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(TagCloudControl), new FrameworkPropertyMetadata
            (typeof(TagCloudControl)));
    }

    public static DependencyProperty tagsProperty = DependencyProperty.Register("tags",
        typeof(IEnumerable),
        typeof(TagCloudControl));

    public CollectionView GroupedTagsView { get; set; }

    public TagCloudControl()
    {

        ItemsSource = Tags;

        //group my labels by "name" property
        GroupedTagsView = (ListCollectionView)CollectionViewSource.GetDefaultView(Tags);
        GroupedTagsView.GroupDescriptions.Add(new PropertyGroupDescription("Name")); 

    }

    public IEnumerable tags
    {
        get { return (IEnumerable)GetValue(tagsProperty); }
        set { SetValue(tagsProperty, value); }
    }
}
  1. There's no reason for you to extend ListBox and not ItemsControl

  2. Why are you going the Custom Control path and not the User Control Path?

What I would do is define a UserControl with a Dependency Property of IEnumerable Tags. I would then bind this DP to the ViewModel (though there's really no need to use MVVM inside controls, but that's really up to you...) and then the ViewModel can go through those strings, and expose another Property - ObservableCollection Tags, Where Tag would be something like this:

class Tag
{
    public String Name {get;set}
    public double Size {get;set;}

}

The XAML would contain just an ItemsControl, that would be databound to the list of Tags in the ViewModel. This way it would work exactly like you want.

<UserControl>
    <ItemsControl ItemsSource="{Binding Tags}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" FontSize="{Binding Size}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</UserControl>

Below is some sample code which I believe does what you are looking for. Max font size is hard coded to 32 but should really be another Dependency Property.

Edit: The control now have DPs for both Tags and Words collections.

XAML:

<UserControl x:Class="TagCloudDemo.TagCloudControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TagCloudDemo="clr-namespace:TagCloudDemo">

<UserControl.Resources>
    <TagCloudDemo:WeightToSizeConverter x:Key="WeightToSizeConverter" />
</UserControl.Resources>

<ScrollViewer HorizontalScrollBarVisibility="Auto">
    <ItemsControl 
        ItemsSource="{Binding Path=Tags, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TagCloudDemo:TagCloudControl}}}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=Name}" FontSize="{Binding Path=Weight, Converter={StaticResource WeightToSizeConverter}}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

Code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace TagCloudDemo
{
    public partial class TagCloudControl : UserControl
    {
        public TagCloudControl()
        {
            InitializeComponent();
        }

        public IEnumerable<string> Words
        {
            get { return (IEnumerable<string>)GetValue(WordsProperty); }
            set { SetValue(WordsProperty, value); }
        }

        public static readonly DependencyProperty WordsProperty =
            DependencyProperty.Register("Words", 
                                        typeof(IEnumerable<string>), 
                                        typeof(TagCloudControl), 
                                        new UIPropertyMetadata(new List<string>(), WordsChanged));

        public IEnumerable<Tag> Tags
        {
            get { return (IEnumerable<Tag>)GetValue(TagsProperty); }
            set { SetValue(TagsProperty, value); }
        }

        public static readonly DependencyProperty TagsProperty =
            DependencyProperty.Register("Tags", 
                                        typeof(IEnumerable<Tag>), 
                                        typeof(TagCloudControl),
                                        new UIPropertyMetadata(new List<Tag>(), TagsChanged));

        private static void WordsChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            TagCloudControl tagCloudControl = sender as TagCloudControl;
            tagCloudControl.Tags = TagCloudDemo.Tag.CreateTags(tagCloudControl.Words);
        }

        private static void TagsChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            TagCloudControl tagCloudControl = sender as TagCloudControl;
            WeightToSizeConverter converter = tagCloudControl.FindResource("WeightToSizeConverter") as WeightToSizeConverter;
            if (converter != null && tagCloudControl.Tags != null)
            {
                converter.MaxWeight = tagCloudControl.Tags.Max(t => t.Weight);
            }
        }
    }

    public class WeightToSizeConverter : IValueConverter
    {
        public int MaxWeight { get; set; }

        #region IValueConverter Members
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int weight = (int)value;
            return 32 * MaxWeight / weight;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
        #endregion IValueConverter Members
    }
}

XAML:

<Window x:Class="TagCloudDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:TagCloudDemo="clr-namespace:TagCloudDemo"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TagCloudDemo:TagCloudControl Tags="{Binding Path=Tags}" Grid.Row="0" Background="Red" />
        <TagCloudDemo:TagCloudControl Words="{Binding Path=Words}" Grid.Row="1" Background="Yellow" />
    </Grid>
</Window>

Code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace TagCloudDemo
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            DataContext = new VM();
        }
    }

    public class VM
    {
        public VM()
        {
            Words = LoadWords();
            Tags = Tag.CreateTags(Words);
        }

        public IEnumerable<string> Words { get; private set; }

        public IEnumerable<Tag> Tags { get; private set; }

        private static IEnumerable<string> LoadWords()
        {
            Random random = new Random();

            string loremIpsum =
                "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
            string[] tokens = loremIpsum.Split(new char[] {' '});
            List<string> words = new List<string>();
            for (int i = 0; i < 500; i++)
            {
                words.Add(tokens[random.Next(tokens.Count())]);
            }
            return words;
        }
    }

    public class Tag
    {
        public Tag(string name, int weight)
        {
            Name = name;
            Weight = weight;
        }

        public string Name { get; set; }
        public int Weight { get; set; }

        public static IEnumerable<Tag> CreateTags(IEnumerable<string> words)
        {
            Dictionary<string, int> tags = new Dictionary<string, int>();
            foreach (string word in words)
            {
                int count = 1;
                if (tags.ContainsKey(word))
                {
                    count = tags[word] + 1;
                }
                tags[word] = count;
            }

            return tags.Select(kvp => new Tag(kvp.Key, kvp.Value));
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM