2009-12-23 12 views
7

अक्षरों की एक अच्छी लग रही लहर कैसे बना सकते हैं मुझे अपने डब्ल्यूपीएफ ऐप में एक लहरदार दिखने वाली टेक्स्ट ऑब्जेक्ट बनाने की आवश्यकता है, और मैं वास्तव में यह मान रहा था कि विकल्प के साथ "पथ के साथ मोड़" होगा, लेकिन मैं मिश्रण में बिल्कुल एक नहीं देखता हूं।डब्ल्यूपीएफ आप

मुझे एक ट्यूटोरियल मिला जो आपको सुझाव देता है कि आपको पाठ द्वारा पथ पथ में पाठ को बदलने के लिए उसे चारों ओर घूमना होगा, लेकिन यह मेरी राय में पूरी तरह से भयानक है, त्रुटि के लिए बहुत अधिक जगह और पर्याप्त लचीलापन नहीं है।

मैं अनिवार्य रूप से एक एनिमेटेड तरंग प्रभाव रखने के लिए एक वाक्य चाहता हूं, मैं इसे कैसे प्राप्त कर सकता हूं?

धन्यवाद सभी मार्क

उत्तर

8

आप चार्ल्स पेज़ॉल्ड के MSDN लेख Render Text On A Path With WPF (archived version here) की जाँच करने के लिए चाहते हो सकता है के साथ कुछ मिल गया लगता है।

wavy text

मैं बहुत उपयोगी इस लेख पाया है और वह भी एक नमूना है, जहां वह एनीमेशन का उपयोग करता है प्रदान करता है।

+0

लिंक के लिए धन्यवाद, मुझे विश्वास नहीं है कि यह लेख मेरे गुगल में नहीं आया है ... – Mark

+0

कोई चिंता नहीं - खुश मैं मदद कर सकता है :-) –

36

आप देख रहे हैं क्या प्रभावी रूप से एक गैर रेखीय बदलना है। दृश्य पर ट्रांसफॉर्म संपत्ति केवल रैखिक परिवर्तन कर सकती है। सौभाग्य से डब्ल्यूपीएफ की 3 डी विशेषताएं आपके बचाव में आती हैं। आप आसानी से पूरा कर सकते हैं क्या आपको लगता है कि इस तरह से इस्तेमाल किया जाएगा एक सरल कस्टम नियंत्रण बनाने के द्वारा तलाश कर रहे हैं:

<local:DisplayOnPath Path="{Binding ...}" Content="Text to display" /> 

तरीके से ऐसा करने के लिए है:

पहले "DisplayOnPath" कस्टम नियंत्रण बनाने।

  1. दृश्य स्टूडियो के कस्टम नियंत्रण टेम्पलेट का उपयोग कर यह बनाएँ (सुनिश्चित करें कि आपके विधानसभा बनाने: ThemeInfo विशेषता ठीक से सेट है और जो कुछ)
  2. प्रकार Geometry की निर्भरता संपत्ति "पथ" (उपयोग wpfdp झलकी)
  3. जोड़े एक केवल पढ़ने के लिए निर्भरता संपत्ति "DisplayMesh" प्रकार Geometry3D की (प्रयोग wpfdpro झलकी) जोड़ें
  4. पथ के लिए एक PropertyChangedCallback जोड़ें Geometry3D के लिए पथ परिवर्तित करने के लिए एक "ComputeDisplayMesh" विधि कॉल करने के लिए, तो सेट DisplayMesh इसे से

वह कुछ इस तरह दिखेगा:

public class DisplayOnPath : ContentControl 
{ 
    static DisplayOnPath() 
    { 
    DefaultStyleKeyProperty.OverrideMetadata ... 
    } 

    public Geometry Path { get { return (Geometry)GetValue(PathProperty) ... 
    public static DependencyProperty PathProperty = ... new UIElementMetadata 
    { 
    PropertyChangedCallback = (obj, e) => 
    { 
     var displayOnPath = obj as DisplayOnPath; 
     displayOnPath.DisplayMesh = ComputeDisplayMesh(displayOnPath.Path); 
    })); 

    public Geometry3D DisplayMesh { get { ... } private set { ... } } 
    private static DependencyPropertyKey DisplayMeshPropertyKey = ... 
    public static DependencyProperty DisplayMeshProperty = ... 
} 

अगला शैली और नियंत्रण Themes/Generic.xaml में (या एक ResourceDictionary यह द्वारा शामिल है) किसी भी कस्टम नियंत्रण के लिए के रूप में टेम्पलेट बनाने के। क्या करता है स्थान के लिए एक DisplayMesh उपयोग करता है और एक ब्रश सामग्री के रूप में अपने नियंत्रण के सामग्री का उपयोग करता है एक 3D मॉडल प्रदर्शित है

<Style TargetType="{x:Type local:DisplayOnPath}"> 

    <Setter Property="Template"> 
    <Setter.Value> 

     <ControlTemplate TargetType="{x:Type local:DisplayOnPath}"> 

     <Viewport3DVisual ...> 

      <ModelVisual3D> 
      <ModelVisual3D.Content> 

       <GeometryModel3D Geometry="{Binding DisplayMesh, RelativeSource={RelativeSource TemplatedParent}}"> 
       <GeometryModel3D.Material> 

        <DiffuseMaterial> 
        <DiffuseMaterial.Brush> 

         <VisualBrush ...> 
         <VisualBrush.Visual> 

          <ContentPresenter /> 
       ... 

: टेम्पलेट इस तरह की सामग्री को होगा।

ध्यान दें कि आपको इच्छित तरीके से काम करने के लिए लेआउट प्राप्त करने के लिए व्यूपोर्ट 3 डीविज़ुअल और विजुअलब्रश पर अन्य गुणों को सेट करने की आवश्यकता हो सकती है और सामग्री दृश्य को उचित रूप से फैलाया जा सकता है।

जो कुछ भी बचा है वह "ComputeDisplayMesh" फ़ंक्शन है। यदि आप सामग्री के शीर्ष (पथ जिन्हें आप प्रदर्शित कर रहे हैं) चाहते हैं तो यह एक छोटा मैपिंग है जो पथ से एक निश्चित दूरी से लंबवत होना चाहिए। बेशक, ऐसे अन्य एल्गोरिदम भी हैं जिन्हें आप चुन सकते हैं, जैसे समानांतर पथ बनाना और प्रत्येक के साथ प्रतिशत दूरी का उपयोग करना।

किसी भी मामले में, बुनियादी एल्गोरिथ्म एक ही है:

  1. अपनी पसंद का एक अनुमानी का उपयोग कर अपने जाल में आयतों की एक उचित संख्या का चयन करें PathGeometry.CreateFromGeometry
  2. का उपयोग कर PathGeometry में बदलें, 'एन',। शायद हार्ड कोडिंग एन = 50 के साथ शुरू करें।
  3. आयताकारों के सभी कोनों के लिए अपने Positions मानों की गणना करें। शीर्ष पर एन + 1 कोनों और नीचे + एन कोनों पर हैं। प्रत्येक निचला कोने PathGeometry.GetPointAtFractionOfLength पर कॉल करके पाया जा सकता है। यह एक स्पर्शरेखा भी देता है, इसलिए शीर्ष कोने को भी ढूंढना आसान है।
  4. अपने TriangleIndices पर गणना करें। यह तुच्छ है। प्रत्येक आयत दो त्रिभुज होंगे, इसलिए प्रति आयताकार छह सूचकांक होंगे।
  5. अपने TextureCoordinates पर गणना करें। यह और भी मामूली है, क्योंकि वे सभी 0, 1, या i/n (जहां मैं आयताकार सूचकांक है)।

ध्यान दें कि अगर आप n के एक निश्चित मूल्य का उपयोग कर रहे हैं, केवल एक चीज आप कभी भी है जब पथ में परिवर्तन recompute करने Posisions सरणी है। बाकी सब कुछ तय है।

यहाँ है क्या इस विधि के मुख्य भाग की तरह दिखता है:

var pathGeometry = PathGeometry.CreateFromGeometry(path); 
int n=50; 

// Compute points in 2D 
var positions = new List<Point>(); 
for(int i=0; i<=n; i++) 
{ 
    Point point, tangent; 
    pathGeometry.GetPointAtFractionOfLength((double)i/n, out point, out tangent); 
    var perpendicular = new Vector(tangent.Y, -tangent.X); 
    perpendicular.Normalize(); 


    positions.Add(point + perpendicular * height); // Top corner 
    positions.Add(point); // Bottom corner 
} 
// Convert to 3D by adding 0 'Z' value 
mesh.Positions = new Point3DCollection(from p in positions select new Point3D(p.X, p.Y, 0)); 

// Now compute the triangle indices, same way 
for(int i=0; i<n; i++) 
{ 
    // First triangle 
    mesh.TriangleIndices.Add(i*2+0); // Upper left 
    mesh.TriangleIndices.Add(i*2+2); // Upper right 
    mesh.TriangleIndices.Add(i*2+1); // Lower left 

    // Second triangle 
    mesh.TriangleIndices.Add(i*2+1); // Lower left 
    mesh.TriangleIndices.Add(i*2+2); // Upper right 
    mesh.TriangleIndices.Add(i*2+3); // Lower right 
} 
// Add code here to create the TextureCoordinates 

इसके बारे में है। अधिकांश कोड ऊपर लिखा गया है। मैं बाकी को भरने के लिए इसे छोड़ देता हूं।

वैसे, ध्यान दें कि 'जेड' मूल्य के साथ रचनात्मक होने के कारण, आप कुछ वाकई शानदार प्रभाव प्राप्त कर सकते हैं।

अद्यतन

मार्क इस के लिए कोड लागू किया है और तीन समस्याओं का सामना करना पड़ा। यहाँ उनके लिए समस्याएं और समाधान कर रहे हैं:

  1. मैं # 1 त्रिकोण के लिए मेरे TriangleIndices क्रम में एक गलती की है। यह ऊपर सही है। मैं मूल रूप से उन सूचकांक ऊपरी बाएं - निचले बाएं - ऊपरी दाएं जा रहा था। त्रिभुज के चारों तरफ घुसपैठ करके हम वास्तव में त्रिकोण के पीछे देखा, इसलिए कुछ भी चित्रित नहीं किया गया था। सूचकांक के क्रम को बदलकर हम घड़ी की दिशा में घूमते हैं ताकि त्रिभुज दिखाई दे।

  2. ज्यामिति मॉडेल 3 डी पर बाध्यकारी मूल रूप से TemplateBinding था। यह काम नहीं करता है क्योंकि टेम्पलेट बाइंडिंग अपडेट को उसी तरह से संभाल नहीं पाती है। इसे नियमित बाध्यकारी में बदलना समस्या को हल करता है।

  3. 3 डी के लिए समन्वय प्रणाली + वाई है, जबकि 2 डी + वाई नीचे है, इसलिए पथ उल्टा दिखाई दिया। इसे कोड में वाई को अस्वीकार कर या ViewPort3DVisual पर, जैसा कि आप चाहें, को हल करके हल किया जा सकता है। मैं व्यक्तिगत रूप से रेंडरट्रांसफॉर्म पसंद करता हूं क्योंकि यह ComputeDisplayMesh कोड को और अधिक पठनीय बनाता है।

यहाँ एक भावना एनिमेट मार्क कोड का एक स्नैपशॉट है मैं हम सभी शेयर लगता है:

Snapshot of animating text "StackOverflowIsFun" http://rayburnsresume.com/StackOverflowImages/WavyLetters.png

+6

+1 आपके उत्तर में दिए गए प्रयासों की तीव्र मात्रा के लिए। कुडोस! –

+4

+1। मुझे आपके उत्तर का आधा समझ में नहीं आया, लेकिन इस तरह के एक पूर्ण स्पष्टीकरण के लिए, आप इसके लायक हैं;) –

+1

यह एक शानदार जवाब है, मुझे इसे पचाने और इसमें शामिल होने के लिए कुछ समय चाहिए ... मैं आपसे वापस आऊंगा मुझे खेलने का मौका मिला है। गंभीरता से, प्रयास के लिए बहुत बहुत धन्यवाद, अगर मैं यह काम कर सकता हूं तो मैं इसे कैसे चाहता हूं, तो यह मेरी परियोजना के लिए इतनी बड़ी जीत होगी, आपको पता नहीं है कि यह लोगों को कितना उत्साहित कर सकता है :) – Mark

0

मैंने सोचा था कि मैं वास्तव में मेरी प्रगति के विवरण पोस्ट ताकि हम से बाहर निकल सकते हैं

<Window.Resources> 
     <Style TargetType="{x:Type local:DisplayOnPath}"> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type local:DisplayOnPath}"> 
         <Viewport3D> 
          <Viewport3D.Camera> 
           <PerspectiveCamera FieldOfView="60" 
               FarPlaneDistance="1000" 
               NearPlaneDistance="10" 
               Position="0,0,300" 
               LookDirection="0,0,-1" 
               UpDirection="0,1,0"/> 
          </Viewport3D.Camera> 
          <ModelVisual3D> 
           <ModelVisual3D.Content> 
            <Model3DGroup> 
             <AmbientLight Color="#ffffff" /> 
             <GeometryModel3D Geometry="{TemplateBinding DisplayMesh}"> 
              <GeometryModel3D.Material> 
               <DiffuseMaterial> 
                <DiffuseMaterial.Brush> 
                 <SolidColorBrush Color="Red" /> 
                </DiffuseMaterial.Brush> 
               </DiffuseMaterial> 
              </GeometryModel3D.Material> 
             </GeometryModel3D> 
            </Model3DGroup> 
           </ModelVisual3D.Content> 
          </ModelVisual3D> 
         </Viewport3D> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
     <Storyboard x:Key="movepath"> 
      <PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="p1" Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[4].(LineSegment.Point)"> 
       <SplinePointKeyFrame KeyTime="00:00:01" Value="181.5,81.5"/> 
      </PointAnimationUsingKeyFrames> 
      <PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="p1" Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[3].(LineSegment.Point)"> 
       <SplinePointKeyFrame KeyTime="00:00:01" Value="141.5,69.5"/> 
      </PointAnimationUsingKeyFrames> 
      <PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="p1" Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[1].(LineSegment.Point)"> 
       <SplinePointKeyFrame KeyTime="00:00:01" Value="62.5,49.5"/> 
      </PointAnimationUsingKeyFrames> 
     </Storyboard> 
    </Window.Resources> 

    <Window.Triggers> 
     <EventTrigger RoutedEvent="FrameworkElement.Loaded"> 
      <BeginStoryboard Storyboard="{StaticResource movepath}"/> 
     </EventTrigger> 
    </Window.Triggers> 

    <Grid x:Name="grid1"> 
    <Path x:Name="p1" Stroke="Black" Margin="238.5,156.5,331.5,0" VerticalAlignment="Top" Height="82"> 
     <Path.Data> 
      <PathGeometry> 
       <PathFigure StartPoint="0.5,0.5"> 
        <LineSegment Point="44.5,15.5"/> 
        <LineSegment Point="73.5,30.5"/> 
        <LineSegment Point="91.5,56.5"/> 
        <LineSegment Point="139.5,53.5"/> 
        <LineSegment Point="161,80"/> 
       </PathFigure> 
      </PathGeometry> 
     </Path.Data> 
    </Path> 
    <local:DisplayOnPath x:Name="wave1" Path="{Binding Data, ElementName=p1, Mode=Default}" /> 
    </Grid> 
: टिप्पणियाँ (जो न के रूप में अच्छा स्वरूपित :))

यहाँ मेरी मुख्य विंडो है

public partial class DisplayOnPath : UserControl 
    { 
     public MeshGeometry3D DisplayMesh 
     { 
      get { return (MeshGeometry3D)GetValue(DisplayMeshProperty); } 
      set { SetValue(DisplayMeshProperty, value); } 
     } 

     public Geometry Path 
     { 
      get { return (Geometry)GetValue(PathProperty); } 
      set { SetValue(PathProperty, value); } 
     } 

     public static readonly DependencyProperty DisplayMeshProperty = 
      DependencyProperty.Register("DisplayMesh", typeof(MeshGeometry3D), typeof(DisplayOnPath), new FrameworkPropertyMetadata(new MeshGeometry3D(), FrameworkPropertyMetadataOptions.AffectsRender)); 

     public static readonly DependencyProperty PathProperty = 
     DependencyProperty.Register("Path", 
            typeof(Geometry), 
            typeof(DisplayOnPath), 
            new PropertyMetadata() 
            { 
             PropertyChangedCallback = (obj, e) => 
             { 
              var displayOnPath = obj as DisplayOnPath; 
              displayOnPath.DisplayMesh = ComputeDisplayMesh(displayOnPath.Path); 
             } 
            } 
     ); 

     private static MeshGeometry3D ComputeDisplayMesh(Geometry path) 
     { 
      var mesh = new MeshGeometry3D(); 

      var pathGeometry = PathGeometry.CreateFromGeometry(path); 
      int n = 50; 
      int height = 10; 

      // Compute points in 2D 
      var positions = new List<Point>(); 
      for (int i = 0; i <= n; i++) 
      { 
       Point point, tangent; 
       pathGeometry.GetPointAtFractionLength((double)i/n, out point, out tangent); 
       var perpendicular = new Vector(tangent.Y, -tangent.X); 
       perpendicular.Normalize(); 
       positions.Add(point + perpendicular * height); // Top corner 
       positions.Add(point); // Bottom corner 
      } 
      // Convert to 3D by adding 0 'Z' value 
      mesh.Positions = new Point3DCollection(from p in positions select new Point3D(p.X, p.Y, 0)); 

      // Now compute the triangle indices, same way 
      for (int i = 0; i < n; i++) 
      { 
       // First triangle 
       mesh.TriangleIndices.Add(i * 2 + 0); // Upper left 
       mesh.TriangleIndices.Add(i * 2 + 1); // Lower left 
       mesh.TriangleIndices.Add(i * 2 + 2); // Upper right 
       // Second triangle 
       mesh.TriangleIndices.Add(i * 2 + 1); // Lower left 
       mesh.TriangleIndices.Add(i * 2 + 2); // Upper right 
       mesh.TriangleIndices.Add(i * 2 + 3); // Lower right 
      } 

      for (int i = 0; i <= n; i++) 
      { 
       for (int j = 0; j < 2; j++) 
       { 
        mesh.TextureCoordinates.Add(new Point((double) i/n, j)); 
       } 
      } 

      //Console.WriteLine("Positions=\"" + mesh.Positions + "\"\nTriangleIndices=\"" + mesh.TriangleIndices + 
      //     "\"\nTextureCoordinates=\"" + mesh.TextureCoordinates + "\""); 
      return mesh; 
     } 

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

     public DisplayOnPath() 
     { 
      InitializeComponent(); 
     } 
    } 

फिलहाल के रूप में है, इस मार्ग के अलावा और कुछ से प्रस्तुत नहीं होती:

तब मैं वास्तविक उपयोगकर्ता नियंत्रण है।

लेकिन अगर आप के बाद खिड़की के लोड होते ही wave1 के जाल जानकारी प्राप्त है, तो बंधन कठिन कोडित किया जा करने के मूल्यों को बदलने, तो आप इस मिल: http://img199.yfrog.com/i/path1.png/

कौन सा 2 मुख्य समस्या है के रूप में यह है:

  1. त्रिभुज सभी बिंदु हैं, इसलिए मुझे लगता है कि आयतों को सही ढंग से परिभाषित नहीं किया जा रहा है
  2. इसके उलट! लेकिन मैं thats स्पर्शरेखा
+1

आपको अपना मूल प्रश्न संपादित करना चाहिए, जवाब जोड़ने के लिए नहीं (क्योंकि यह कोई जवाब नहीं है) –

संबंधित मुद्दे