简体   繁体   中英

Can WPF Data Bind Without Going Through a Windows Control?

This will take some explaining.

I'm writing a tool in WPF / C# to dynamically generate, in the view, a visual graph of the data in the view-model. The top-most parent is a grid, and each horizontal row of data is a canvas (inside a border). The canvas holds all the other UI elements (like TextBlocks).

I have a class to hold each row of UI elements in the view.xaml.cs, defined like this:

class ReportRow
{
    public Border Divider;
    public Canvas Row;
    public TextBlock Title;
    public List<TextBlock> Phases = new List<TextBlock>();
}

Then I define the entire graph as a List of these Rows:

List<ReportRow> reportRows = new List<ReportRow>();

In the viewmodel.cs, I have the data listed as an ObservableCollection so I can data bind to it and access the data from the view:

public ObservableCollection<SDDeliverable> Deliverables
{
    get
    {
        return this.deliverables;
    }

    private set
    {
        this.deliverables = value;
        this.RaisePropertyChanged(() => this.Deliverables);
    }
}

Back in the view, I loop through the ObservableCollection, creating the rows and assigning the data to the elements (shown without all the styling and positioning, for brevity):

reportRows.Add(new ReportRow());
reportRows[i].Divider = new Border();
ProjectDisplay.Children.Add(reportRows[i].Divider);
reportRows[i].Row = new Canvas();
reportRows[i].Divider.Child = reportRows[i].Row;
reportRows[i].Title = new TextBlock();
reportRows[i].Title.SetBinding(TextBlock.TextProperty, new Binding(string.Format("Deliverables[{0}].DeliverableTitle", i)));
reportRows[i].Row.Children.Add(reportRows[i].Title);

Now, my original problem was that, because I'm binding each individual member of the collection (rather than binding the whole collection to one UI element, like a ListView), the view has no idea how long the collection is, which means I can't use a "foreach" or a loop counter. It worked fine with an arbitrary number of rows, but I didn't want to have to guess.

What I did was add a new label to the UI, bound to the length of the collection, and disguised it as a bit of helpful info:

<Label x:Name="DeliverableCountLabel" Content="{Binding Path=DeliverableCount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

this.DeliverableCount = Deliverables.Count;

Once I got that number into a view control, I was able to use it as my loop counter:

int totalItems;
if (int.TryParse(DeliverableCountLabel.Content.ToString(), out totalItems))
{
    for (int i = 0; i < totalItems; i += 1)
    {
        reportRows.Add(new ReportRow());

This is a hacky fix, but I was alright with using it once.

The new problem is that once wasn't enough. As I add more data to the model, I'm hitting the same problem. Each Deliverable has a list of Phases and each Phase has a list of Tasks. I don't want to clutter up the UI with number labels all over the place.

I feel like there should be a way to use a data binding without having it go through the xaml or a visual control element. I just want to bind a variable in the view to a variable in the view-model so I can look at certain bits of info that I don't necessarily want to show the user.

I started messing with doing it this way:

Binding testBinding = new Binding("DeliverableCount");

However, it's the next step that's confounding me. Everything I've tried past that point has been incorrect somehow.

// returns the binding object itself, not the bound value
testBinding.ToString();

// error (not a real thing you can do, apparently)
string testString;
testString.SetBinding (testBinding);

How do I send a value from view-model to view without having to display it on-screen somewhere? Am I going about this the wrong way? Is this even possible?

A last-ditch idea I have is to create one dummy label and either make it invisible somehow or hide it behind another element. Then I could write a function to update the data binding on this one specific label any time I needed to access something in the view-model that's not shown on-screen. However, this really feels like a hack of a hack and I'd rather not go down that road unless it's really the best (or only) option.

This is how I ended up solving this. It's hacky, but it works.

I created a label in the xaml and set it's visibility to hidden. Then I just call one of these functions:

    public string TempStringBind(string bind)
    {
        DummyLabel.SetBinding(Label.ContentProperty, new Binding(bind));
        return DummyLabel.Content.ToString();
    }

   public int TempIntBind(string bind)
    {
        DummyLabel.SetBinding(Label.ContentProperty, new Binding(bind));

        int newInt;
        if (DummyLabel.Content != null && int.TryParse(DummyLabel.Content.ToString(), out newInt))
        {
            return newInt;
        }
        else
        {
            return -1;
        }
    }

This will take any variable from the view-model that can be bound to, bind it to an invisible label, grab that value from the label, and return it to me in the view in a useable form. While it's still going through something in the view xaml, the benefit is that the user doesn't have to see a bunch of extra controls or data they don't care about just to find out how many rows or columns I need to make for lists.

The ItemsSource method would be a lot cleaner, but that only works if I'm sticking the data in an existing control, like a ListView or a ComboBox, which aren't good for making visual charts and graphs with exact positioning.

I'm not sure what you guys meant by it not being MVVM. I've got the M, the V, and the VM all in there. :P

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