1

I have a custom control that i need to inherit from GridView. And standard way, that works for UserControl,

<UserControl.Resources>
    <local:NodeToImageConverter x:Key="imageConverter" />
    <local:ImageHeightConverter x:Key="imageHeightConverter" />
</UserControl.Resources>

it does not work for me. I really would like to use local resources - not to put them higher. I tried to make one level class and declare a dependency property for resourses there. But it did not work:

public partial class CustomDetailsView2 : GridViewRes {
    public CustomDetailsView2() {
        InitializeComponent();
    } // it is interesting that if i set a breakpoint here i see my resourses - 2 items
}
public class GridViewRes : GridView {
    public static DependencyProperty ResourcesProperty = DependencyProperty.Register(
        "Resources",
        typeof(ResourceDictionary),
        typeof(CustomDetailsView2),
        new FrameworkPropertyMetadata() {
            DefaultValue = new ResourceDictionary()
        });
    public ResourceDictionary Resources {
        get { return (ResourceDictionary)GetValue(ResourcesProperty); }
        set { SetValue(ResourcesProperty, value); }
    }
}

This is xaml

<local:GridViewRes x:Class="Nexplorer.UIParts.CustomDetailsView2"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Nexplorer.UIParts"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">

<local:GridViewRes.Resources>
    <local:NodeToImageConverter x:Key="imageConverter" />
    <local:ImageHeightConverter x:Key="imageHeightConverter" />
</local:GridViewRes.Resources>

i tried also attached property - with no success
<local:CustomProperties.Resources>
    <local:NodeToImageConverter x:Key="imageConverter" />
    <local:ImageHeightConverter x:Key="imageHeightConverter" />
</local:CustomProperties.Resources>

<GridViewColumn Header="Name">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <Grid  HorizontalAlignment="Stretch">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>

                <Image Grid.Column="0" Stretch="Fill" HorizontalAlignment="Left">
                    <Image.Source>
                        <MultiBinding Converter="{StaticResource imageConverter}"> here i have a runtime "imageConverter resourse not found" exception...
                            <Binding Path="IconSize" ElementName="thisControl"/>
                            <Binding />
                        </MultiBinding>
                    </Image.Source>
                </Image>
                <TextBlock Grid.Column="1" Text="{Binding Path=DisplayName}" VerticalAlignment="Center" Margin="2 1 0 1"/>

                <Grid.Height>
                    <Binding Path="IconSize" Converter="{StaticResource imageHeightConverter}" ElementName="thisControl" />
                </Grid.Height>
            </Grid>
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Size">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=DisplaySize}" />
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Modified">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=DisplayModifiedDateTime}" />
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>

</local:GridViewRes>

how can i declare resourses in the this xaml?

Neco
  • 539
  • 4
  • 10
  • You can try to move their resources in `Grid.Resources` which is in the `CellTemplate`. – Anatoliy Nikolaev Mar 08 '14 at 19:34
  • Are you sure that ownerType of Resources DP is `typeof(CustomDetailsView2)`. I think it should be `typeof(GridViewRes)`. – Lukas Kubis Mar 08 '14 at 19:40
  • @Lukas Kubis: It does not help. As I understand it, he did it for the test. – Anatoliy Nikolaev Mar 08 '14 at 19:43
  • `I have a custom control that i need to inherit from GridView` - What for? you don't subclass WPF UI elements unless you have a `strong` reason to. – Federico Berasategui Mar 08 '14 at 19:48
  • @AnatoliyNikolaev, I can use CellTemplate level, but it will be individually for every column. In this particular case it will work, but I would like to have a general solution. – Neco Mar 08 '14 at 20:56
  • @LukasKubis, it does not help. Anatoliy is right - i did several attempts on order to make it working and this is left here after experiments. – Neco Mar 08 '14 at 20:58
  • @HighCore, I have a list view that has property which is dynamically set to several different views (two at this moment) - all of them are inherited from ViewBase. One is declared like this: – Neco Mar 08 '14 at 21:08
  • @HighCore, I have a list view that has property which is dynamically set to several different views (two at this moment) - all of them are inherited from ViewBase. One is "very custom" and I found a solution with inheritance from ViewBase and defning styles in resourse dictionary (overriding DefaultStyleKey and ItemContainerDefaultStyleKey). The second one is very similar to standard GridView, so i decided to inherit it from GridView and just little bit customize (define columns etc.) in xaml. But in fact this is a topic for a separate discussion. )) – Neco Mar 08 '14 at 21:17
  • @Neco you're probably doing it all wrong. I have made dozens of "very custom" WPF UIs and to this day I never had the need to subclass a UI element, nor create a `ViewBase` or the like. Post a screenshot of what you need and I can tell you the proper way to do it in WPF. – Federico Berasategui Mar 08 '14 at 21:22
  • @HighCore, I think it is quite simple (but i spent a lot of time to do it). I am creating a file explorer with two view modes - list and details. list is here: http://s24.postimg.org/dbjz672n9/list.jpg and details is here: http://s10.postimg.org/3qzfgpcsp/details.jpg - the list is "very custom" because WPF does not have needed mode out of box. – Neco Mar 08 '14 at 21:33

2 Answers2

5

Ok. Delete all your code and start all over.

If you're working with WPF, you need to leave behind all the traditional approaches you might have used in the past in ancient UI frameworks, and understand and embrace The WPF Mentality.

Basically, you never have to subclass WPF UI elements (or Controls) unless you want to create Control-specific functionality. Changing any UI elements (or it's children) visual appearance is achieved in WPF using Styles and Templates.

This is how you do that in WPF:

<Window x:Class="WpfApplication1.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>
        <DataTemplate x:Key="TilesTemplate">
            <ListView ItemsSource="{Binding Files}">
                <ListView.Template>
                    <ControlTemplate TargetType="ListView">
                        <WrapPanel IsItemsHost="True" ItemWidth="200"/>
                    </ControlTemplate>
                </ListView.Template>

                <ListView.ItemTemplate>
                    <DataTemplate>
                        <DockPanel>
                            <Image DockPanel.Dock="Left" Height="32" Width="32" Margin="5"
                                   Source="http://files.softicons.com/download/internet-icons/bremen-icons-by-pc.de/png/32/folder.png"/>

                            <StackPanel>
                                <TextBlock Text="{Binding Name}" FontWeight="Bold" TextTrimming="CharacterEllipsis"/>
                                <TextBlock Text="{Binding Length}" Foreground="Gray"/>
                            </StackPanel>
                        </DockPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </DataTemplate>

        <DataTemplate x:Key="DetailTemplate">
            <ListView ItemsSource="{Binding Files}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="File Name" DisplayMemberBinding="{Binding Name}"/>
                        <GridViewColumn Header="Size" DisplayMemberBinding="{Binding Length}"/>
                    </GridView>
                </ListView.View>
            </ListView>
        </DataTemplate>

    </Window.Resources>

    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
            <RadioButton Content="Tiles" IsChecked="{Binding TilesMode}"/>
            <RadioButton Content="Details" IsChecked="{Binding DetailsMode}"/>
        </StackPanel>

        <ContentControl Content="{Binding}">
            <ContentControl.Style>
                <Style TargetType="ContentControl">
                    <Style.Triggers>
                        <!-- This DataTrigger will change the View Mode to Tiles -->
                        <DataTrigger Binding="{Binding TilesMode}" Value="True">
                            <Setter Property="ContentTemplate" Value="{StaticResource TilesTemplate}"/>
                        </DataTrigger>

                        <!-- This DataTrigger will change the View Mode to Details -->
                        <DataTrigger Binding="{Binding DetailsMode}" Value="True">
                            <Setter Property="ContentTemplate" Value="{StaticResource DetailTemplate}"/>
                        </DataTrigger>

                    </Style.Triggers>
                </Style>
            </ContentControl.Style>
        </ContentControl>
    </DockPanel>
</Window>

Code Behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DataContext = new ExplorerViewModel();
    }
}

ViewModel:

public class ExplorerViewModel: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    private bool _detailsMode;
    public bool DetailsMode
    {
        get { return _detailsMode; }
        set 
        {
            _detailsMode = value;
            OnPropertyChanged();
        }
    }

    private bool _tilesMode;
    public bool TilesMode
    {
        get { return _tilesMode; }
        set
        {
            _tilesMode = value;
            OnPropertyChanged();
        }
    }

    public ObservableCollection<FileInfo> Files { get; set; } 

    public ExplorerViewModel()
    {
        var path = @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE";

        Files = new ObservableCollection<FileInfo>(Directory.GetFiles(path).Select(x => new FileInfo(x)));

        TilesMode = true;
    }
}

Result:

enter image description here

  • Notice how I'm using standard WPF UI elements such as ListView and concepts such as DataTemplate as explained in the above link.

  • Notice that this approach is really clean because it requires much less code and it keeps UI completely separate from data by using proper DataBinding.

  • As opposed to traditional UI frameworks (such as winforms) where you can't customize anything and you're forced to use the default stuff, WPF has a different approach. It doesn't have too many "Out of the Box" UIs because the level of customizability is huge, and you can easily create a 100% custom UI in no-time (as shown here).

  • When running this example, make sure the path variable in the ExplorerViewModel constructor points to a valid path on your hard drive.

  • WPF Rocks. - just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • thank you for your answer. In fact I started with code like yours, but "TilesMode" was not like I need. I need it to look and behave like Windows Explorer (items go first top-bottom and then left-rigth with horisontal scrollbar) and I googled a lot to come up to the solution with inheritance from ViewBase. There is no much code in my CustomListView for now, but I expect CustomDetailsView may contain much more code at least to store/display particular columns (saved by user in config). But WPF rules and you are probably right - I need to ask a separate question on how to do this. – Neco Mar 09 '14 at 02:34
  • @Neco if you want top-bottom and then left-right it is just a matter of setting the WrapPanel to Vertical Orientation, like this: ``. Adding a `ScrollViewer to the ControlTemplate will also provide the scrolling functionality. – Federico Berasategui Mar 09 '14 at 03:08
0

There is actually an easy way to do this. It's the paradigm all auto-generated UserControls and Windows use as well but it's not restricted to them, you can even use it when subclassing.

Take a look how the stock MainWindow class is defined in code and XAML - the one that is auto-generated for you when you create a new WPF application project. It's using the base type in XAML (you will see that it begins with <Window x:Class="WpfApplication1.MainWindow"> instead of its derived type, MainWindow. The same should work with your GridView just as well, just use its base type, not local:GridViewRes. You can then use all its inherited properties, including Resources, just as well as anywhere else. The only limitation is that you cannot assign a value in your code-behind XAML to a property you newly defined in your derived class, but I can hardly think of a scenario when it's useful - you can do it in code just as well (while of course anyone consuming your control will be able to do it in XAML, including the properties inherited from GridView).

Of course, as was already said, one should consider carefully whether he really needs to subclass a WPF control. It brings its share of problems and often much cleaner solution is to wrap it into an UserControl and export only the few properties you want to share with the world. Just my two cents.

Deirh
  • 91
  • 1
  • 4
  • I am quite new in WPF, so I may not fully understand your point, but I think I followed the same way to come to the code I have. I also checked how it is done for Window and UserControl, but I found that Resourses property is owned by FrameworkElement which is base class for Window and UserControl, but not for GridView. That is why created additional local:GridViewRes, created Resourses property in it and tried to use it - without success. Could you please clarify if I miss something? – Neco Mar 08 '14 at 21:22
  • I am sorry, you are completely right. I have confused GridView for a ListView. GridView, being only a view definition and not a control, indeed does offer much less. Let me see if I can come up with a solution that works for it as well. – Deirh Mar 08 '14 at 21:32
  • Do you need a custom class and functionality for each GridView you want to use? If you can define it fully in XAML then I suggest you create a ResourceDictionary of your GridView(s) and add them as resources there. You can have local resources in this ResourceDictionary, used by and shared between the GridViews, without making an appearance anywhere else. Consider it a repository of your views. You can then reference it into any ListView as {StaticResource NameOfYourView} after referencing such ResourceDictionary. – Deirh Mar 08 '14 at 21:40