2013-04-02 51 views
0

我有兩個TextBlock的,我連續定位在畫布上。在第一種情況下正常工作:試圖理解使用BeginInvoke模式()

TextBlock text1 = new TextBlock(); 
text1.Text = "Not "; 
text1.FontSize = 18; 
Canvas.SetTop(text1, 20); 
Canvas.SetLeft(text1, 20); 
canvas.Children.Add(text1); 

TextBlock text2 = new TextBlock(); 
text2.Text = "bad!"; 
text2.FontSize = 18; 
Canvas.SetTop(text2, 20); 
canvas.Dispatcher.BeginInvoke(DispatcherPriority.Background, 
    new DispatcherOperationCallback(delegate(Object state) 
    { 
    Canvas.SetLeft(text2, 20 + text1.ActualWidth); 
    return null; 
    } 
), null); 
canvas.Children.Add(text2); 

結果:

enter image description here

但是,第二種情況下,不使用的BeginInvoke(),將失敗:

TextBlock text1 = new TextBlock(); 
text1.Text = "Not "; 
text1.FontSize = 18; 
Canvas.SetTop(text1, 20); 
Canvas.SetLeft(text1, 20); 
canvas.Children.Add(text1); 

TextBlock text2 = new TextBlock(); 
text2.Text = "bad!"; 
text2.FontSize = 18; 
Canvas.SetTop(text2, 20); 
Canvas.SetLeft(text2, 20 + text1.ActualWidth); // ActualWidth is zero. 
canvas.Children.Add(text2); 

結果:

enter image description here

現在,我知道在第二種情況下,WPF渲染還沒有發生。我的問題是這樣的:在這種情況下,我需要知道僅在渲染髮生後纔可用的UI控件的實際座標值的情況下使用的首選模式是什麼?

(例如是將至,在使用的BeginInvoke(),一個很好的解決方案應該整個代碼被封裝在一個巨大的BeginInvoke()?)

+1

老兄,我不知道你在做什麼,但是你的代碼太可怕了。你爲什麼要在代碼中創建UI元素?這就是XAML的用途。 –

+0

單詞將在運行時讀入。我無法將它們硬編碼到XAML中。 – Sabuncu

+1

然後創建一個'ItemsControl'或其他東西。您也可以將它們放在一個TextBlock中。否則使用'WrapPanel'或其他東西。 Canvas不是Text的正確容器。 WPF不是winforms。你的代碼仍然很糟糕。如果不同'TextBlock'之間的屬性相同,則使用XAML定義的樣式。發佈你需要的截圖,我可以告訴你在WPF中實現它的正確方法。 –

回答

2

要回答你的問題:

Dispatcher.BeginInvoke()在分派器的「等待作業」隊列中排隊操作。這使其能夠處理第一個UI元素的添加,並在繼續執行代碼之前運行LayoutRender遍。

因此,當你的代碼運行時,第一個TextBlock的大小已經被計算出來了,你可以得到它。

同樣,我不知道你在試圖做什麼,但是在代碼中創建UI元素通常是設計不佳的標誌。 WPF不是winforms,而WPF的方式與在winforms中做任何事情所需的可怕黑客完全不同。

編輯:

這是使用WrapPanel和一些RenderTransform我的方法:

<Window x:Class="MiscSamples.MovingWords" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MovingWords" Height="300" Width="300"> 
    <ItemsControl ItemsSource="{Binding}"> 
     <ItemsControl.ItemsPanel> 
      <ItemsPanelTemplate> 
       <WrapPanel IsItemsHost="True"/> 
      </ItemsPanelTemplate> 
     </ItemsControl.ItemsPanel> 
     <ItemsControl.ItemTemplate> 
      <DataTemplate> 
       <Thumb DragDelta="Thumb_DragDelta" Margin="2"> 
        <Thumb.Template> 
         <ControlTemplate> 
          <TextBlock Text="{Binding Text}" 
             FontSize="{Binding FontSize}" 
             Foreground="{Binding Color}"/> 
         </ControlTemplate> 
        </Thumb.Template> 
        <Thumb.RenderTransform> 
         <TranslateTransform X="{Binding OffsetX}" Y="{Binding OffsetY}"/> 
        </Thumb.RenderTransform> 
       </Thumb> 
      </DataTemplate> 
     </ItemsControl.ItemTemplate> 
    </ItemsControl> 
</Window> 

代碼背後:

public partial class MovingWords : Window 
{ 
    public ObservableCollection<MovingWordModel> Words { get; set; } 

    public MovingWords() 
    { 
     InitializeComponent(); 

     Words = new ObservableCollection<MovingWordModel> 
      { 
       new MovingWordModel() {Color = "Black", FontSize = 18, Text = "Hello!!"}, 
       new MovingWordModel() {Color = "Black", FontSize = 18, Text = "This"}, 
       new MovingWordModel() {Color = "Black", FontSize = 18, Text = "is"}, 
       new MovingWordModel() {Color = "Black", FontSize = 18, Text = "the"}, 
       new MovingWordModel() {Color = "Black", FontSize = 18, Text = "Power"}, 
       new MovingWordModel() {Color = "Black", FontSize = 18, Text = "of"}, 
       new MovingWordModel() {Color = "Blue", FontSize = 18, Text = "WPF"}, 
      }; 

     DataContext = Words; 
    } 

    private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e) 
    { 
     var thumb = sender as Thumb; 
     if (thumb == null) 
      return; 

     var word = thumb.DataContext as MovingWordModel; 

     if (word == null) 
      return; 

     word.OffsetX += e.HorizontalChange; 
     word.OffsetY += e.VerticalChange; 
    } 
} 

數據模型:

public class MovingWordModel:PropertyChangedBase 
{ 
    public string Text { get; set; } 

    public int FontSize { get; set; } 

    public string Color { get; set; } 

    private double _offsetX; 
    public Double OffsetX 
    { 
     get { return _offsetX; } 
     set 
     { 
      _offsetX = value; 
      OnPropertyChanged("OffsetX"); 
     } 
    } 

    private double _offsetY; 
    public double OffsetY 
    { 
     get { return _offsetY; } 
     set 
     { 
      _offsetY = value; 
      OnPropertyChanged("OffsetY"); 
     } 
    } 
} 

PropertyChangedBase:

public class PropertyChangedBase:INotifyPropertyChanged 
    { 
     public event PropertyChangedEventHandler PropertyChanged; 

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

您可以單擊並拖動話對他們走動。

請注意,拖動的值將存儲在OffsetXOffsetY屬性中。這種方法唯一的問題是你有些失去分辨率獨立性,因爲偏移值實際上會將它們從默認位置移動(這是由WrapPanel決定的,因此它們可能會根據WrapPanel的大小本身)。

+0

你會提出什麼建議可以讓我在工作區域(簡單的矩形框)以任何方式安排單詞?這些詞可以在任何地方,它們不限於在行或列上。即真正的自由格式。當我放置一個單詞時,我需要知道它是ActualWidth,以便我可以在旁邊放置另一個單詞而不重疊。 – Sabuncu

+1

@Sabuncu事後你會做什麼?你需要保存這些新職位嗎?因爲你也可以使用WrapPanel或其他東西,然後爲拖動應用一個'RenderTransform'。 –

+0

是的,單詞將一起放回到句子表單中,並且需要保存所有更改。「爲拖動應用RenderTransform」 - >翻遍我的腦海,我是WPF的新手,但帶有正確的指導,我可以解決它。 – Sabuncu